From 3dd2f4af573addd561f80991e72ab3ce6c834d5a Mon Sep 17 00:00:00 2001 From: tonihei Date: Thu, 11 May 2017 02:31:13 -0700 Subject: [PATCH] Tests for timeline repeat mode support. Checking the expected next/previous window indices and if the correct window or period gets returned. TimelineTest defines mock classes and verification methods used by the specific implementation tests. ------------- Created by MOE: https://github.com/google/moe MOE_MIGRATED_REVID=155727385 --- .../android/exoplayer2/TimelineTest.java | 251 ++++++++++++++++++ .../source/ClippingMediaSourceTest.java | 60 ++--- .../source/ConcatenatingMediaSourceTest.java | 111 ++++++++ .../source/LoopingMediaSourceTest.java | 91 +++++++ 4 files changed, 475 insertions(+), 38 deletions(-) create mode 100644 library/core/src/androidTest/java/com/google/android/exoplayer2/TimelineTest.java create mode 100644 library/core/src/androidTest/java/com/google/android/exoplayer2/source/ConcatenatingMediaSourceTest.java create mode 100644 library/core/src/androidTest/java/com/google/android/exoplayer2/source/LoopingMediaSourceTest.java diff --git a/library/core/src/androidTest/java/com/google/android/exoplayer2/TimelineTest.java b/library/core/src/androidTest/java/com/google/android/exoplayer2/TimelineTest.java new file mode 100644 index 0000000000..fc3ccacbf2 --- /dev/null +++ b/library/core/src/androidTest/java/com/google/android/exoplayer2/TimelineTest.java @@ -0,0 +1,251 @@ +/* + * Copyright (C) 2017 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.google.android.exoplayer2; + +import com.google.android.exoplayer2.ExoPlayer.RepeatMode; +import com.google.android.exoplayer2.Timeline.Period; +import com.google.android.exoplayer2.Timeline.Window; +import com.google.android.exoplayer2.source.MediaPeriod; +import com.google.android.exoplayer2.source.MediaSource; +import com.google.android.exoplayer2.source.MediaSource.Listener; +import com.google.android.exoplayer2.upstream.Allocator; +import java.io.IOException; +import junit.framework.TestCase; + +/** + * Unit test for {@link Timeline}. + */ +public class TimelineTest extends TestCase { + + /** + * Fake timeline with multiple periods and user-defined window id. + */ + public static final class FakeTimeline extends Timeline { + + private static final int WINDOW_DURATION_US = 1000000; + + private final int periodCount; + private final int id; + + public FakeTimeline(int periodCount, int id) { + this.periodCount = periodCount; + this.id = id; + } + + @Override + public int getWindowCount() { + return 1; + } + + @Override + public Window getWindow(int windowIndex, Window window, boolean setIds, + long defaultPositionProjectionUs) { + return window.set(id, 0, 0, true, false, 0, WINDOW_DURATION_US, 0, periodCount - 1, 0); + } + + @Override + public int getPeriodCount() { + return periodCount; + } + + @Override + public Period getPeriod(int periodIndex, Period period, boolean setIds) { + return period.set(new int[] { id, periodIndex }, null, 0, WINDOW_DURATION_US, 0, false); + } + + @Override + public int getIndexOfPeriod(Object uid) { + return C.INDEX_UNSET; + } + } + + /** + * Returns a stub {@link MediaSource} with the specified {@link Timeline} in its source info. + */ + public static MediaSource stubMediaSourceSourceWithTimeline(final Timeline timeline) { + return new MediaSource() { + @Override + public void prepareSource(ExoPlayer player, boolean isTopLevelSource, Listener listener) { + listener.onSourceInfoRefreshed(timeline, null); + } + + @Override + public void maybeThrowSourceInfoRefreshError() throws IOException { + } + + @Override + public MediaPeriod createPeriod(int index, Allocator allocator, long positionUs) { + return null; + } + + @Override + public void releasePeriod(MediaPeriod mediaPeriod) { + } + + @Override + public void releaseSource() { + } + }; + } + + /** + * Works in conjunction with {@code stubMediaSourceSourceWithTimeline} to extract the Timeline + * from a media source. + */ + public static Timeline extractTimelineFromMediaSource(MediaSource mediaSource) { + class TimelineListener implements Listener { + private Timeline timeline; + @Override + public void onSourceInfoRefreshed(Timeline timeline, Object manifest) { + this.timeline = timeline; + } + } + TimelineListener listener = new TimelineListener(); + mediaSource.prepareSource(null, true, listener); + return listener.timeline; + } + + /** + * Verify the behaviour of {@link Timeline#getNextWindowIndex(int, int)}, + * {@link Timeline#getPreviousWindowIndex(int, int)}, + * {@link Timeline#getWindow(int, Window, boolean)}, + * {@link Timeline#getNextPeriodIndex(int, Period, Window, int)}, and + * {@link Timeline#getPeriod(int, Period, boolean)}. + */ + public static final class TimelineVerifier { + + private final Timeline timeline; + + public TimelineVerifier(Timeline timeline) { + this.timeline = timeline; + } + + public TimelineVerifier assertWindowIds(int... expectedWindowIds) { + Window window = new Window(); + assertEquals(expectedWindowIds.length, timeline.getWindowCount()); + for (int i = 0; i < timeline.getWindowCount(); i++) { + timeline.getWindow(i, window, true); + assertEquals(expectedWindowIds[i], window.id); + } + return this; + } + + public TimelineVerifier assertPreviousWindowIndices(@RepeatMode int repeatMode, + int... expectedPreviousWindowIndices) { + for (int i = 0; i < timeline.getWindowCount(); i++) { + assertEquals(expectedPreviousWindowIndices[i], + timeline.getPreviousWindowIndex(i, repeatMode)); + } + return this; + } + + public TimelineVerifier assertNextWindowIndices(@RepeatMode int repeatMode, + int... expectedNextWindowIndices) { + for (int i = 0; i < timeline.getWindowCount(); i++) { + assertEquals(expectedNextWindowIndices[i], + timeline.getNextWindowIndex(i, repeatMode)); + } + return this; + } + + public TimelineVerifier assertPeriodCounts(int... expectedPeriodCounts) { + int windowCount = timeline.getWindowCount(); + int[] accumulatedPeriodCounts = new int[windowCount + 1]; + accumulatedPeriodCounts[0] = 0; + for (int i = 0; i < windowCount; i++) { + accumulatedPeriodCounts[i + 1] = accumulatedPeriodCounts[i] + expectedPeriodCounts[i]; + } + assertEquals(accumulatedPeriodCounts[accumulatedPeriodCounts.length - 1], + timeline.getPeriodCount()); + Window window = new Window(); + Period period = new Period(); + for (int i = 0; i < windowCount; i++) { + timeline.getWindow(i, window, true); + assertEquals(accumulatedPeriodCounts[i], window.firstPeriodIndex); + assertEquals(accumulatedPeriodCounts[i + 1] - 1, window.lastPeriodIndex); + } + int expectedWindowIndex = 0; + for (int i = 0; i < timeline.getPeriodCount(); i++) { + timeline.getPeriod(i, period, true); + while (i >= accumulatedPeriodCounts[expectedWindowIndex + 1]) { + expectedWindowIndex++; + } + assertEquals(expectedWindowIndex, period.windowIndex); + assertEquals(i - accumulatedPeriodCounts[expectedWindowIndex], ((int[]) period.id)[1]); + if (i < accumulatedPeriodCounts[expectedWindowIndex + 1] - 1) { + assertEquals(i + 1, timeline.getNextPeriodIndex(i, period, window, + ExoPlayer.REPEAT_MODE_OFF)); + assertEquals(i + 1, timeline.getNextPeriodIndex(i, period, window, + ExoPlayer.REPEAT_MODE_ONE)); + assertEquals(i + 1, timeline.getNextPeriodIndex(i, period, window, + ExoPlayer.REPEAT_MODE_ALL)); + } else { + int nextWindowOff = timeline.getNextWindowIndex(expectedWindowIndex, + ExoPlayer.REPEAT_MODE_OFF); + int nextWindowOne = timeline.getNextWindowIndex(expectedWindowIndex, + ExoPlayer.REPEAT_MODE_ONE); + int nextWindowAll = timeline.getNextWindowIndex(expectedWindowIndex, + ExoPlayer.REPEAT_MODE_ALL); + int nextPeriodOff = nextWindowOff == C.INDEX_UNSET ? C.INDEX_UNSET + : accumulatedPeriodCounts[nextWindowOff]; + int nextPeriodOne = nextWindowOne == C.INDEX_UNSET ? C.INDEX_UNSET + : accumulatedPeriodCounts[nextWindowOne]; + int nextPeriodAll = nextWindowAll == C.INDEX_UNSET ? C.INDEX_UNSET + : accumulatedPeriodCounts[nextWindowAll]; + assertEquals(nextPeriodOff, timeline.getNextPeriodIndex(i, period, window, + ExoPlayer.REPEAT_MODE_OFF)); + assertEquals(nextPeriodOne, timeline.getNextPeriodIndex(i, period, window, + ExoPlayer.REPEAT_MODE_ONE)); + assertEquals(nextPeriodAll, timeline.getNextPeriodIndex(i, period, window, + ExoPlayer.REPEAT_MODE_ALL)); + } + } + return this; + } + } + + public void testEmptyTimeline() { + new TimelineVerifier(Timeline.EMPTY) + .assertWindowIds() + .assertPeriodCounts(); + } + + public void testSinglePeriodTimeline() { + Timeline timeline = new FakeTimeline(1, 111); + new TimelineVerifier(timeline) + .assertWindowIds(111) + .assertPeriodCounts(1) + .assertPreviousWindowIndices(ExoPlayer.REPEAT_MODE_OFF, C.INDEX_UNSET) + .assertPreviousWindowIndices(ExoPlayer.REPEAT_MODE_ONE, 0) + .assertPreviousWindowIndices(ExoPlayer.REPEAT_MODE_ALL, 0) + .assertNextWindowIndices(ExoPlayer.REPEAT_MODE_OFF, C.INDEX_UNSET) + .assertNextWindowIndices(ExoPlayer.REPEAT_MODE_ONE, 0) + .assertNextWindowIndices(ExoPlayer.REPEAT_MODE_ALL, 0); + } + + public void testMultiPeriodTimeline() { + Timeline timeline = new FakeTimeline(5, 111); + new TimelineVerifier(timeline) + .assertWindowIds(111) + .assertPeriodCounts(5) + .assertPreviousWindowIndices(ExoPlayer.REPEAT_MODE_OFF, C.INDEX_UNSET) + .assertPreviousWindowIndices(ExoPlayer.REPEAT_MODE_ONE, 0) + .assertPreviousWindowIndices(ExoPlayer.REPEAT_MODE_ALL, 0) + .assertNextWindowIndices(ExoPlayer.REPEAT_MODE_OFF, C.INDEX_UNSET) + .assertNextWindowIndices(ExoPlayer.REPEAT_MODE_ONE, 0) + .assertNextWindowIndices(ExoPlayer.REPEAT_MODE_ALL, 0); + } +} diff --git a/library/core/src/androidTest/java/com/google/android/exoplayer2/source/ClippingMediaSourceTest.java b/library/core/src/androidTest/java/com/google/android/exoplayer2/source/ClippingMediaSourceTest.java index 0933fb858b..f570272bef 100644 --- a/library/core/src/androidTest/java/com/google/android/exoplayer2/source/ClippingMediaSourceTest.java +++ b/library/core/src/androidTest/java/com/google/android/exoplayer2/source/ClippingMediaSourceTest.java @@ -15,20 +15,15 @@ */ package com.google.android.exoplayer2.source; -import static org.mockito.Mockito.doAnswer; - import android.test.InstrumentationTestCase; import com.google.android.exoplayer2.C; import com.google.android.exoplayer2.ExoPlayer; import com.google.android.exoplayer2.Timeline; import com.google.android.exoplayer2.Timeline.Period; import com.google.android.exoplayer2.Timeline.Window; -import com.google.android.exoplayer2.source.MediaSource.Listener; -import com.google.android.exoplayer2.testutil.TestUtil; -import org.mockito.Mock; -import org.mockito.Mockito; -import org.mockito.invocation.InvocationOnMock; -import org.mockito.stubbing.Answer; +import com.google.android.exoplayer2.TimelineTest; +import com.google.android.exoplayer2.TimelineTest.FakeTimeline; +import com.google.android.exoplayer2.TimelineTest.TimelineVerifier; /** * Unit tests for {@link ClippingMediaSource}. @@ -38,15 +33,11 @@ public final class ClippingMediaSourceTest extends InstrumentationTestCase { private static final long TEST_PERIOD_DURATION_US = 1000000; private static final long TEST_CLIP_AMOUNT_US = 300000; - @Mock - private MediaSource mockMediaSource; - private Timeline clippedTimeline; private Window window; private Period period; @Override protected void setUp() throws Exception { - TestUtil.setUpMockito(this); window = new Timeline.Window(); period = new Timeline.Period(); } @@ -109,35 +100,28 @@ public final class ClippingMediaSourceTest extends InstrumentationTestCase { clippedTimeline.getPeriod(0, period).getDurationUs()); } + public void testWindowAndPeriodIndices() { + Timeline timeline = new FakeTimeline(1, 111); + Timeline clippedTimeline = getClippedTimeline(timeline, TEST_CLIP_AMOUNT_US, + TEST_PERIOD_DURATION_US - TEST_CLIP_AMOUNT_US); + new TimelineVerifier(clippedTimeline) + .assertWindowIds(111) + .assertPeriodCounts(1) + .assertPreviousWindowIndices(ExoPlayer.REPEAT_MODE_OFF, C.INDEX_UNSET) + .assertPreviousWindowIndices(ExoPlayer.REPEAT_MODE_ONE, 0) + .assertPreviousWindowIndices(ExoPlayer.REPEAT_MODE_ALL, 0) + .assertNextWindowIndices(ExoPlayer.REPEAT_MODE_OFF, C.INDEX_UNSET) + .assertNextWindowIndices(ExoPlayer.REPEAT_MODE_ONE, 0) + .assertNextWindowIndices(ExoPlayer.REPEAT_MODE_ALL, 0); + } + /** * Wraps the specified timeline in a {@link ClippingMediaSource} and returns the clipped timeline. */ - private Timeline getClippedTimeline(Timeline timeline, long startMs, long endMs) { - mockMediaSourceSourceWithTimeline(timeline); - new ClippingMediaSource(mockMediaSource, startMs, endMs).prepareSource(null, true, - new Listener() { - @Override - public void onSourceInfoRefreshed(Timeline timeline, Object manifest) { - clippedTimeline = timeline; - } - }); - return clippedTimeline; - } - - /** - * Returns a mock {@link MediaSource} with the specified {@link Timeline} in its source info. - */ - private MediaSource mockMediaSourceSourceWithTimeline(final Timeline timeline) { - doAnswer(new Answer() { - @Override - public Void answer(InvocationOnMock invocation) throws Throwable { - MediaSource.Listener listener = (MediaSource.Listener) invocation.getArguments()[2]; - listener.onSourceInfoRefreshed(timeline, null); - return null; - } - }).when(mockMediaSource).prepareSource(Mockito.any(ExoPlayer.class), Mockito.anyBoolean(), - Mockito.any(MediaSource.Listener.class)); - return mockMediaSource; + private static Timeline getClippedTimeline(Timeline timeline, long startMs, long endMs) { + MediaSource mediaSource = TimelineTest.stubMediaSourceSourceWithTimeline(timeline); + return TimelineTest.extractTimelineFromMediaSource( + new ClippingMediaSource(mediaSource, startMs, endMs)); } } 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 new file mode 100644 index 0000000000..08d2c1cda7 --- /dev/null +++ b/library/core/src/androidTest/java/com/google/android/exoplayer2/source/ConcatenatingMediaSourceTest.java @@ -0,0 +1,111 @@ +/* + * Copyright (C) 2016 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.google.android.exoplayer2.source; + +import com.google.android.exoplayer2.C; +import com.google.android.exoplayer2.ExoPlayer; +import com.google.android.exoplayer2.Timeline; +import com.google.android.exoplayer2.TimelineTest; +import com.google.android.exoplayer2.TimelineTest.FakeTimeline; +import com.google.android.exoplayer2.TimelineTest.TimelineVerifier; +import junit.framework.TestCase; + +/** + * Unit tests for {@link ConcatenatingMediaSource}. + */ +public final class ConcatenatingMediaSourceTest extends TestCase { + + public void testSingleMediaSource() { + Timeline timeline = getConcatenatedTimeline(false, new FakeTimeline(3, 111)); + new TimelineVerifier(timeline) + .assertWindowIds(111) + .assertPeriodCounts(3) + .assertPreviousWindowIndices(ExoPlayer.REPEAT_MODE_OFF, C.INDEX_UNSET) + .assertPreviousWindowIndices(ExoPlayer.REPEAT_MODE_ONE, 0) + .assertPreviousWindowIndices(ExoPlayer.REPEAT_MODE_ALL, 0) + .assertNextWindowIndices(ExoPlayer.REPEAT_MODE_OFF, C.INDEX_UNSET) + .assertNextWindowIndices(ExoPlayer.REPEAT_MODE_ONE, 0) + .assertNextWindowIndices(ExoPlayer.REPEAT_MODE_ALL, 0); + + timeline = getConcatenatedTimeline(true, new FakeTimeline(3, 111)); + new TimelineVerifier(timeline) + .assertWindowIds(111) + .assertPeriodCounts(3) + .assertPreviousWindowIndices(ExoPlayer.REPEAT_MODE_OFF, C.INDEX_UNSET) + .assertPreviousWindowIndices(ExoPlayer.REPEAT_MODE_ONE, 0) + .assertPreviousWindowIndices(ExoPlayer.REPEAT_MODE_ALL, 0) + .assertNextWindowIndices(ExoPlayer.REPEAT_MODE_OFF, C.INDEX_UNSET) + .assertNextWindowIndices(ExoPlayer.REPEAT_MODE_ONE, 0) + .assertNextWindowIndices(ExoPlayer.REPEAT_MODE_ALL, 0); + } + + public void testMultipleMediaSources() { + Timeline[] timelines = { new FakeTimeline(3, 111), new FakeTimeline(1, 222), + new FakeTimeline(3, 333) }; + Timeline timeline = getConcatenatedTimeline(false, timelines); + new TimelineVerifier(timeline) + .assertWindowIds(111, 222, 333) + .assertPeriodCounts(3, 1, 3) + .assertPreviousWindowIndices(ExoPlayer.REPEAT_MODE_OFF, C.INDEX_UNSET, 0, 1) + .assertPreviousWindowIndices(ExoPlayer.REPEAT_MODE_ONE, 0, 1, 2) + .assertPreviousWindowIndices(ExoPlayer.REPEAT_MODE_ALL, 2, 0, 1) + .assertNextWindowIndices(ExoPlayer.REPEAT_MODE_OFF, 1, 2, C.INDEX_UNSET) + .assertNextWindowIndices(ExoPlayer.REPEAT_MODE_ONE, 0, 1, 2) + .assertNextWindowIndices(ExoPlayer.REPEAT_MODE_ALL, 1, 2, 0); + + timeline = getConcatenatedTimeline(true, timelines); + new TimelineVerifier(timeline) + .assertWindowIds(111, 222, 333) + .assertPeriodCounts(3, 1, 3) + .assertPreviousWindowIndices(ExoPlayer.REPEAT_MODE_OFF, C.INDEX_UNSET, 0, 1) + .assertPreviousWindowIndices(ExoPlayer.REPEAT_MODE_ONE, 2, 0, 1) + .assertPreviousWindowIndices(ExoPlayer.REPEAT_MODE_ALL, 2, 0, 1) + .assertNextWindowIndices(ExoPlayer.REPEAT_MODE_OFF, 1, 2, C.INDEX_UNSET) + .assertNextWindowIndices(ExoPlayer.REPEAT_MODE_ONE, 1, 2, 0) + .assertNextWindowIndices(ExoPlayer.REPEAT_MODE_ALL, 1, 2, 0); + } + + public void testNestedMediaSources() { + Timeline timeline = getConcatenatedTimeline(false, + getConcatenatedTimeline(false, new FakeTimeline(1, 111), new FakeTimeline(1, 222)), + getConcatenatedTimeline(true, new FakeTimeline(1, 333), new FakeTimeline(1, 444))); + new TimelineVerifier(timeline) + .assertWindowIds(111, 222, 333, 444) + .assertPeriodCounts(1, 1, 1, 1) + .assertPreviousWindowIndices(ExoPlayer.REPEAT_MODE_OFF, C.INDEX_UNSET, 0, 1, 2) + .assertPreviousWindowIndices(ExoPlayer.REPEAT_MODE_ONE, 0, 1, 3, 2) + .assertPreviousWindowIndices(ExoPlayer.REPEAT_MODE_ALL, 3, 0, 1, 2) + .assertNextWindowIndices(ExoPlayer.REPEAT_MODE_OFF, 1, 2, 3, C.INDEX_UNSET) + .assertNextWindowIndices(ExoPlayer.REPEAT_MODE_ONE, 0, 1, 3, 2) + .assertNextWindowIndices(ExoPlayer.REPEAT_MODE_ALL, 1, 2, 3, 0); + } + + /** + * Wraps the specified timelines in a {@link ConcatenatingMediaSource} and returns + * the concatenated timeline. + */ + private static Timeline getConcatenatedTimeline(boolean isRepeatOneAtomic, + Timeline... timelines) { + MediaSource[] mediaSources = new MediaSource[timelines.length]; + for (int i = 0; i < timelines.length; i++) { + mediaSources[i] = TimelineTest.stubMediaSourceSourceWithTimeline(timelines[i]); + } + return TimelineTest.extractTimelineFromMediaSource( + new ConcatenatingMediaSource(isRepeatOneAtomic, mediaSources)); + } + + +} diff --git a/library/core/src/androidTest/java/com/google/android/exoplayer2/source/LoopingMediaSourceTest.java b/library/core/src/androidTest/java/com/google/android/exoplayer2/source/LoopingMediaSourceTest.java new file mode 100644 index 0000000000..7b4a449764 --- /dev/null +++ b/library/core/src/androidTest/java/com/google/android/exoplayer2/source/LoopingMediaSourceTest.java @@ -0,0 +1,91 @@ +/* + * Copyright (C) 2017 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.google.android.exoplayer2.source; + +import com.google.android.exoplayer2.C; +import com.google.android.exoplayer2.ExoPlayer; +import com.google.android.exoplayer2.Timeline; +import com.google.android.exoplayer2.TimelineTest; +import com.google.android.exoplayer2.TimelineTest.FakeTimeline; +import com.google.android.exoplayer2.TimelineTest.TimelineVerifier; +import junit.framework.TestCase; + +/** + * Unit tests for {@link LoopingMediaSource}. + */ +public class LoopingMediaSourceTest extends TestCase { + + private final Timeline multiWindowTimeline; + + public LoopingMediaSourceTest() { + multiWindowTimeline = TimelineTest.extractTimelineFromMediaSource( + new ConcatenatingMediaSource( + TimelineTest.stubMediaSourceSourceWithTimeline(new FakeTimeline(1, 111)), + TimelineTest.stubMediaSourceSourceWithTimeline(new FakeTimeline(1, 222)), + TimelineTest.stubMediaSourceSourceWithTimeline(new FakeTimeline(1, 333)))); + } + + public void testSingleLoop() { + Timeline timeline = getLoopingTimeline(multiWindowTimeline, 1); + new TimelineVerifier(timeline) + .assertWindowIds(111, 222, 333) + .assertPeriodCounts(1, 1, 1) + .assertPreviousWindowIndices(ExoPlayer.REPEAT_MODE_OFF, C.INDEX_UNSET, 0, 1) + .assertPreviousWindowIndices(ExoPlayer.REPEAT_MODE_ONE, 0, 1, 2) + .assertPreviousWindowIndices(ExoPlayer.REPEAT_MODE_ALL, 2, 0, 1) + .assertNextWindowIndices(ExoPlayer.REPEAT_MODE_OFF, 1, 2, C.INDEX_UNSET) + .assertNextWindowIndices(ExoPlayer.REPEAT_MODE_ONE, 0, 1, 2) + .assertNextWindowIndices(ExoPlayer.REPEAT_MODE_ALL, 1, 2, 0); + } + + public void testMultiLoop() { + Timeline timeline = getLoopingTimeline(multiWindowTimeline, 3); + new TimelineVerifier(timeline) + .assertWindowIds(111, 222, 333, 111, 222, 333, 111, 222, 333) + .assertPeriodCounts(1, 1, 1, 1, 1, 1, 1, 1, 1) + .assertPreviousWindowIndices(ExoPlayer.REPEAT_MODE_OFF, + C.INDEX_UNSET, 0, 1, 2, 3, 4, 5, 6, 7, 8) + .assertPreviousWindowIndices(ExoPlayer.REPEAT_MODE_ONE, 0, 1, 2, 3, 4, 5, 6, 7, 8) + .assertPreviousWindowIndices(ExoPlayer.REPEAT_MODE_ALL, 8, 0, 1, 2, 3, 4, 5, 6, 7) + .assertNextWindowIndices(ExoPlayer.REPEAT_MODE_OFF, 1, 2, 3, 4, 5, 6, 7, 8, C.INDEX_UNSET) + .assertNextWindowIndices(ExoPlayer.REPEAT_MODE_ONE, 0, 1, 2, 3, 4, 5, 6, 7, 8) + .assertNextWindowIndices(ExoPlayer.REPEAT_MODE_ALL, 1, 2, 3, 4, 5, 6, 7, 8, 0); + } + + public void testInfiniteLoop() { + Timeline timeline = getLoopingTimeline(multiWindowTimeline, Integer.MAX_VALUE); + new TimelineVerifier(timeline) + .assertWindowIds(111, 222, 333) + .assertPeriodCounts(1, 1, 1) + .assertPreviousWindowIndices(ExoPlayer.REPEAT_MODE_OFF, 2, 0, 1) + .assertPreviousWindowIndices(ExoPlayer.REPEAT_MODE_ONE, 0, 1, 2) + .assertPreviousWindowIndices(ExoPlayer.REPEAT_MODE_ALL, 2, 0, 1) + .assertNextWindowIndices(ExoPlayer.REPEAT_MODE_OFF, 1, 2, 0) + .assertNextWindowIndices(ExoPlayer.REPEAT_MODE_ONE, 0, 1, 2) + .assertNextWindowIndices(ExoPlayer.REPEAT_MODE_ALL, 1, 2, 0); + } + + /** + * Wraps the specified timeline in a {@link LoopingMediaSource} and returns + * the looping timeline. + */ + private static Timeline getLoopingTimeline(Timeline timeline, int loopCount) { + MediaSource mediaSource = TimelineTest.stubMediaSourceSourceWithTimeline(timeline); + return TimelineTest.extractTimelineFromMediaSource( + new LoopingMediaSource(mediaSource, loopCount)); + } + +}