Commit 30b31b56 by tonihei Committed by Oliver Woodman

Support empty concatenations and empty timelines in concatenations.

Both cases were not supported so far. Added tests which all failed in the
previous code version and adapted the concatenated media sources to cope with
empty timelines and empty concatenations.

-------------
Created by MOE: https://github.com/google/moe
MOE_MIGRATED_REVID=166480344
parent 01f48198
...@@ -31,11 +31,24 @@ import junit.framework.TestCase; ...@@ -31,11 +31,24 @@ import junit.framework.TestCase;
*/ */
public final class ConcatenatingMediaSourceTest extends TestCase { public final class ConcatenatingMediaSourceTest extends TestCase {
public void testEmptyConcatenation() {
for (boolean atomic : new boolean[] {false, true}) {
Timeline timeline = getConcatenatedTimeline(atomic);
TimelineAsserts.assertEmpty(timeline);
timeline = getConcatenatedTimeline(atomic, Timeline.EMPTY);
TimelineAsserts.assertEmpty(timeline);
timeline = getConcatenatedTimeline(atomic, Timeline.EMPTY, Timeline.EMPTY, Timeline.EMPTY);
TimelineAsserts.assertEmpty(timeline);
}
}
public void testSingleMediaSource() { public void testSingleMediaSource() {
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);
for (boolean shuffled : new boolean[] { false, true }) { for (boolean shuffled : new boolean[] {false, true}) {
TimelineAsserts.assertPreviousWindowIndices(timeline, Player.REPEAT_MODE_OFF, shuffled, TimelineAsserts.assertPreviousWindowIndices(timeline, Player.REPEAT_MODE_OFF, shuffled,
C.INDEX_UNSET); C.INDEX_UNSET);
TimelineAsserts.assertPreviousWindowIndices(timeline, Player.REPEAT_MODE_ONE, shuffled, 0); TimelineAsserts.assertPreviousWindowIndices(timeline, Player.REPEAT_MODE_ONE, shuffled, 0);
...@@ -49,7 +62,7 @@ public final class ConcatenatingMediaSourceTest extends TestCase { ...@@ -49,7 +62,7 @@ public final class ConcatenatingMediaSourceTest extends TestCase {
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);
for (boolean shuffled : new boolean[] { false, true }) { for (boolean shuffled : new boolean[] {false, true}) {
TimelineAsserts.assertPreviousWindowIndices(timeline, Player.REPEAT_MODE_OFF, shuffled, TimelineAsserts.assertPreviousWindowIndices(timeline, Player.REPEAT_MODE_OFF, shuffled,
C.INDEX_UNSET); C.INDEX_UNSET);
TimelineAsserts.assertPreviousWindowIndices(timeline, Player.REPEAT_MODE_ONE, shuffled, 0); TimelineAsserts.assertPreviousWindowIndices(timeline, Player.REPEAT_MODE_ONE, shuffled, 0);
...@@ -91,7 +104,7 @@ public final class ConcatenatingMediaSourceTest extends TestCase { ...@@ -91,7 +104,7 @@ public final class ConcatenatingMediaSourceTest extends TestCase {
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);
for (boolean shuffled : new boolean[] { false, true }) { for (boolean shuffled : new boolean[] {false, true}) {
TimelineAsserts.assertPreviousWindowIndices(timeline, Player.REPEAT_MODE_OFF, shuffled, 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, shuffled, TimelineAsserts.assertPreviousWindowIndices(timeline, Player.REPEAT_MODE_ONE, shuffled,
...@@ -135,6 +148,54 @@ public final class ConcatenatingMediaSourceTest extends TestCase { ...@@ -135,6 +148,54 @@ public final class ConcatenatingMediaSourceTest extends TestCase {
TimelineAsserts.assertNextWindowIndices(timeline, Player.REPEAT_MODE_ALL, true, 2, 0, 3, 1); TimelineAsserts.assertNextWindowIndices(timeline, Player.REPEAT_MODE_ALL, true, 2, 0, 3, 1);
} }
public void testEmptyTimelineMediaSources() {
// Empty timelines in the front, back, and the middle (single and multiple in a row).
Timeline[] timelines = { Timeline.EMPTY, createFakeTimeline(1, 111), Timeline.EMPTY,
Timeline.EMPTY, createFakeTimeline(2, 222), Timeline.EMPTY, createFakeTimeline(3, 333),
Timeline.EMPTY };
Timeline timeline = getConcatenatedTimeline(false, timelines);
TimelineAsserts.assertWindowIds(timeline, 111, 222, 333);
TimelineAsserts.assertPeriodCounts(timeline, 1, 2, 3);
TimelineAsserts.assertPreviousWindowIndices(timeline, Player.REPEAT_MODE_OFF, false,
C.INDEX_UNSET, 0, 1);
TimelineAsserts.assertPreviousWindowIndices(timeline, Player.REPEAT_MODE_ONE, false, 0, 1, 2);
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, 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, 1, 2, 3);
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));
}
}
/** /**
* Wraps the specified timelines in a {@link ConcatenatingMediaSource} and returns * Wraps the specified timelines in a {@link ConcatenatingMediaSource} and returns
* the concatenated timeline. * the concatenated timeline.
......
...@@ -15,6 +15,7 @@ ...@@ -15,6 +15,7 @@
*/ */
package com.google.android.exoplayer2.source; package com.google.android.exoplayer2.source;
import android.os.ConditionVariable;
import android.os.Handler; import android.os.Handler;
import android.os.HandlerThread; import android.os.HandlerThread;
import android.os.Looper; import android.os.Looper;
...@@ -25,7 +26,10 @@ import com.google.android.exoplayer2.ExoPlayer; ...@@ -25,7 +26,10 @@ import com.google.android.exoplayer2.ExoPlayer;
import com.google.android.exoplayer2.PlaybackParameters; import com.google.android.exoplayer2.PlaybackParameters;
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.MediaPeriod.Callback;
import com.google.android.exoplayer2.source.MediaSource.Listener; import com.google.android.exoplayer2.source.MediaSource.Listener;
import com.google.android.exoplayer2.source.MediaSource.MediaPeriodId;
import com.google.android.exoplayer2.testutil.FakeMediaPeriod;
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.FakeShuffleOrder;
import com.google.android.exoplayer2.testutil.FakeTimeline; import com.google.android.exoplayer2.testutil.FakeTimeline;
...@@ -53,13 +57,13 @@ public final class DynamicConcatenatingMediaSourceTest extends TestCase { ...@@ -53,13 +57,13 @@ public final class DynamicConcatenatingMediaSourceTest extends TestCase {
DynamicConcatenatingMediaSource mediaSource = new DynamicConcatenatingMediaSource( DynamicConcatenatingMediaSource mediaSource = new DynamicConcatenatingMediaSource(
new FakeShuffleOrder(0)); new FakeShuffleOrder(0));
prepareAndListenToTimelineUpdates(mediaSource); prepareAndListenToTimelineUpdates(mediaSource);
assertNotNull(timeline);
waitForTimelineUpdate(); waitForTimelineUpdate();
TimelineAsserts.assertEmpty(timeline); TimelineAsserts.assertEmpty(timeline);
// Add first source. // Add first source.
mediaSource.addMediaSource(childSources[0]); mediaSource.addMediaSource(childSources[0]);
waitForTimelineUpdate(); waitForTimelineUpdate();
assertNotNull(timeline);
TimelineAsserts.assertPeriodCounts(timeline, 1); TimelineAsserts.assertPeriodCounts(timeline, 1);
TimelineAsserts.assertWindowIds(timeline, 111); TimelineAsserts.assertWindowIds(timeline, 111);
...@@ -143,6 +147,9 @@ public final class DynamicConcatenatingMediaSourceTest extends TestCase { ...@@ -143,6 +147,9 @@ public final class DynamicConcatenatingMediaSourceTest extends TestCase {
assertEquals(timeline.getWindowCount() - 1, timeline.getFirstWindowIndex(true)); assertEquals(timeline.getWindowCount() - 1, timeline.getFirstWindowIndex(true));
assertEquals(0, timeline.getLastWindowIndex(true)); assertEquals(0, timeline.getLastWindowIndex(true));
// Assert all periods can be prepared.
assertAllPeriodsCanBeCreatedPreparedAndReleased(mediaSource, timeline.getPeriodCount());
// Remove at front of queue. // Remove at front of queue.
mediaSource.removeMediaSource(0); mediaSource.removeMediaSource(0);
waitForTimelineUpdate(); waitForTimelineUpdate();
...@@ -192,6 +199,7 @@ public final class DynamicConcatenatingMediaSourceTest extends TestCase { ...@@ -192,6 +199,7 @@ public final class DynamicConcatenatingMediaSourceTest extends TestCase {
TimelineAsserts.assertPreviousWindowIndices(timeline, Player.REPEAT_MODE_OFF, true, TimelineAsserts.assertPreviousWindowIndices(timeline, Player.REPEAT_MODE_OFF, true,
1, 2, C.INDEX_UNSET); 1, 2, C.INDEX_UNSET);
assertAllPeriodsCanBeCreatedPreparedAndReleased(mediaSource, timeline.getPeriodCount());
mediaSource.releaseSource(); mediaSource.releaseSource();
for (int i = 1; i < 4; i++) { for (int i = 1; i < 4; i++) {
childSources[i].assertReleased(); childSources[i].assertReleased();
...@@ -225,8 +233,9 @@ public final class DynamicConcatenatingMediaSourceTest extends TestCase { ...@@ -225,8 +233,9 @@ public final class DynamicConcatenatingMediaSourceTest extends TestCase {
TimelineAsserts.assertPeriodCounts(timeline, 1, 9); TimelineAsserts.assertPeriodCounts(timeline, 1, 9);
TimelineAsserts.assertWindowIds(timeline, 111, 999); TimelineAsserts.assertWindowIds(timeline, 111, 999);
TimelineAsserts.assertWindowIsDynamic(timeline, false, false); TimelineAsserts.assertWindowIsDynamic(timeline, false, false);
assertAllPeriodsCanBeCreatedPreparedAndReleased(mediaSource, timeline.getPeriodCount());
//Add lazy sources after preparation //Add lazy sources after preparation (and also try to prepare media period from lazy source).
mediaSource.addMediaSource(1, lazySources[2]); mediaSource.addMediaSource(1, lazySources[2]);
waitForTimelineUpdate(); waitForTimelineUpdate();
mediaSource.addMediaSource(2, childSources[1]); mediaSource.addMediaSource(2, childSources[1]);
...@@ -239,17 +248,90 @@ public final class DynamicConcatenatingMediaSourceTest extends TestCase { ...@@ -239,17 +248,90 @@ public final class DynamicConcatenatingMediaSourceTest extends TestCase {
TimelineAsserts.assertWindowIds(timeline, null, 111, 222, 999); TimelineAsserts.assertWindowIds(timeline, null, 111, 222, 999);
TimelineAsserts.assertWindowIsDynamic(timeline, true, false, false, false); TimelineAsserts.assertWindowIsDynamic(timeline, true, false, false, false);
MediaPeriod lazyPeriod = mediaSource.createPeriod(new MediaPeriodId(0), null);
assertNotNull(lazyPeriod);
final ConditionVariable lazyPeriodPrepared = new ConditionVariable();
lazyPeriod.prepare(new Callback() {
@Override
public void onPrepared(MediaPeriod mediaPeriod) {
lazyPeriodPrepared.open();
}
@Override
public void onContinueLoadingRequested(MediaPeriod source) {}
}, 0);
assertFalse(lazyPeriodPrepared.block(1));
MediaPeriod secondLazyPeriod = mediaSource.createPeriod(new MediaPeriodId(0), null);
assertNotNull(secondLazyPeriod);
mediaSource.releasePeriod(secondLazyPeriod);
lazySources[3].triggerTimelineUpdate(createFakeTimeline(7)); lazySources[3].triggerTimelineUpdate(createFakeTimeline(7));
waitForTimelineUpdate(); waitForTimelineUpdate();
TimelineAsserts.assertPeriodCounts(timeline, 8, 1, 2, 9); TimelineAsserts.assertPeriodCounts(timeline, 8, 1, 2, 9);
TimelineAsserts.assertWindowIds(timeline, 888, 111, 222, 999); TimelineAsserts.assertWindowIds(timeline, 888, 111, 222, 999);
TimelineAsserts.assertWindowIsDynamic(timeline, false, false, false, false); TimelineAsserts.assertWindowIsDynamic(timeline, false, false, false, false);
assertTrue(lazyPeriodPrepared.block(TIMEOUT_MS));
mediaSource.releasePeriod(lazyPeriod);
mediaSource.releaseSource(); mediaSource.releaseSource();
childSources[0].assertReleased(); childSources[0].assertReleased();
childSources[1].assertReleased(); childSources[1].assertReleased();
} }
public void testEmptyTimelineMediaSource() throws InterruptedException {
timeline = null;
DynamicConcatenatingMediaSource mediaSource = new DynamicConcatenatingMediaSource(
new FakeShuffleOrder(0));
prepareAndListenToTimelineUpdates(mediaSource);
assertNotNull(timeline);
waitForTimelineUpdate();
TimelineAsserts.assertEmpty(timeline);
mediaSource.addMediaSource(new FakeMediaSource(Timeline.EMPTY, null));
waitForTimelineUpdate();
TimelineAsserts.assertEmpty(timeline);
mediaSource.addMediaSources(Arrays.asList(new MediaSource[] {
new FakeMediaSource(Timeline.EMPTY, null), new FakeMediaSource(Timeline.EMPTY, null),
new FakeMediaSource(Timeline.EMPTY, null), new FakeMediaSource(Timeline.EMPTY, null),
new FakeMediaSource(Timeline.EMPTY, null), new FakeMediaSource(Timeline.EMPTY, null)
}));
waitForTimelineUpdate();
TimelineAsserts.assertEmpty(timeline);
// Insert non-empty media source to leave empty sources at the start, the end, and the middle
// (with single and multiple empty sources in a row).
MediaSource[] mediaSources = createMediaSources(3);
mediaSource.addMediaSource(1, mediaSources[0]);
waitForTimelineUpdate();
mediaSource.addMediaSource(4, mediaSources[1]);
waitForTimelineUpdate();
mediaSource.addMediaSource(6, mediaSources[2]);
waitForTimelineUpdate();
TimelineAsserts.assertWindowIds(timeline, 111, 222, 333);
TimelineAsserts.assertPeriodCounts(timeline, 1, 2, 3);
TimelineAsserts.assertPreviousWindowIndices(timeline, Player.REPEAT_MODE_OFF, false,
C.INDEX_UNSET, 0, 1);
TimelineAsserts.assertPreviousWindowIndices(timeline, Player.REPEAT_MODE_ONE, false, 0, 1, 2);
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, 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));
assertAllPeriodsCanBeCreatedPreparedAndReleased(mediaSource, timeline.getPeriodCount());
}
public void testIllegalArguments() { public void testIllegalArguments() {
DynamicConcatenatingMediaSource mediaSource = new DynamicConcatenatingMediaSource(); DynamicConcatenatingMediaSource mediaSource = new DynamicConcatenatingMediaSource();
MediaSource validSource = new FakeMediaSource(createFakeTimeline(1), null); MediaSource validSource = new FakeMediaSource(createFakeTimeline(1), null);
...@@ -325,6 +407,28 @@ public final class DynamicConcatenatingMediaSourceTest extends TestCase { ...@@ -325,6 +407,28 @@ public final class DynamicConcatenatingMediaSourceTest extends TestCase {
return new FakeTimeline(new TimelineWindowDefinition(index + 1, (index + 1) * 111)); return new FakeTimeline(new TimelineWindowDefinition(index + 1, (index + 1) * 111));
} }
private static void assertAllPeriodsCanBeCreatedPreparedAndReleased(MediaSource mediaSource,
int periodCount) {
for (int i = 0; i < periodCount; i++) {
MediaPeriod mediaPeriod = mediaSource.createPeriod(new MediaPeriodId(i), null);
assertNotNull(mediaPeriod);
final ConditionVariable mediaPeriodPrepared = new ConditionVariable();
mediaPeriod.prepare(new Callback() {
@Override
public void onPrepared(MediaPeriod mediaPeriod) {
mediaPeriodPrepared.open();
}
@Override
public void onContinueLoadingRequested(MediaPeriod source) {}
}, 0);
assertTrue(mediaPeriodPrepared.block(TIMEOUT_MS));
MediaPeriod secondMediaPeriod = mediaSource.createPeriod(new MediaPeriodId(i), null);
assertNotNull(secondMediaPeriod);
mediaSource.releasePeriod(secondMediaPeriod);
mediaSource.releasePeriod(mediaPeriod);
}
}
private static class LazyMediaSource implements MediaSource { private static class LazyMediaSource implements MediaSource {
private Listener listener; private Listener listener;
...@@ -344,7 +448,7 @@ public final class DynamicConcatenatingMediaSourceTest extends TestCase { ...@@ -344,7 +448,7 @@ public final class DynamicConcatenatingMediaSourceTest extends TestCase {
@Override @Override
public MediaPeriod createPeriod(MediaPeriodId id, Allocator allocator) { public MediaPeriod createPeriod(MediaPeriodId id, Allocator allocator) {
return null; return new FakeMediaPeriod(TrackGroupArray.EMPTY);
} }
@Override @Override
......
...@@ -42,7 +42,7 @@ public class LoopingMediaSourceTest extends TestCase { ...@@ -42,7 +42,7 @@ public class LoopingMediaSourceTest extends TestCase {
Timeline timeline = getLoopingTimeline(multiWindowTimeline, 1); Timeline timeline = getLoopingTimeline(multiWindowTimeline, 1);
TimelineAsserts.assertWindowIds(timeline, 111, 222, 333); TimelineAsserts.assertWindowIds(timeline, 111, 222, 333);
TimelineAsserts.assertPeriodCounts(timeline, 1, 1, 1); TimelineAsserts.assertPeriodCounts(timeline, 1, 1, 1);
for (boolean shuffled : new boolean[] { false, true }) { for (boolean shuffled : new boolean[] {false, true}) {
TimelineAsserts.assertPreviousWindowIndices(timeline, Player.REPEAT_MODE_OFF, shuffled, 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, shuffled, TimelineAsserts.assertPreviousWindowIndices(timeline, Player.REPEAT_MODE_ONE, shuffled,
...@@ -60,7 +60,7 @@ public class LoopingMediaSourceTest extends TestCase { ...@@ -60,7 +60,7 @@ public class LoopingMediaSourceTest extends TestCase {
Timeline timeline = getLoopingTimeline(multiWindowTimeline, 3); Timeline timeline = getLoopingTimeline(multiWindowTimeline, 3);
TimelineAsserts.assertWindowIds(timeline, 111, 222, 333, 111, 222, 333, 111, 222, 333); TimelineAsserts.assertWindowIds(timeline, 111, 222, 333, 111, 222, 333, 111, 222, 333);
TimelineAsserts.assertPeriodCounts(timeline, 1, 1, 1, 1, 1, 1, 1, 1, 1); TimelineAsserts.assertPeriodCounts(timeline, 1, 1, 1, 1, 1, 1, 1, 1, 1);
for (boolean shuffled : new boolean[] { false, true }) { for (boolean shuffled : new boolean[] {false, true}) {
TimelineAsserts.assertPreviousWindowIndices(timeline, Player.REPEAT_MODE_OFF, shuffled, TimelineAsserts.assertPreviousWindowIndices(timeline, Player.REPEAT_MODE_OFF, shuffled,
C.INDEX_UNSET, 0, 1, 2, 3, 4, 5, 6, 7, 8); C.INDEX_UNSET, 0, 1, 2, 3, 4, 5, 6, 7, 8);
TimelineAsserts.assertPreviousWindowIndices(timeline, Player.REPEAT_MODE_ONE, shuffled, TimelineAsserts.assertPreviousWindowIndices(timeline, Player.REPEAT_MODE_ONE, shuffled,
...@@ -80,7 +80,7 @@ public class LoopingMediaSourceTest extends TestCase { ...@@ -80,7 +80,7 @@ public class LoopingMediaSourceTest extends TestCase {
Timeline timeline = getLoopingTimeline(multiWindowTimeline, Integer.MAX_VALUE); Timeline timeline = getLoopingTimeline(multiWindowTimeline, Integer.MAX_VALUE);
TimelineAsserts.assertWindowIds(timeline, 111, 222, 333); TimelineAsserts.assertWindowIds(timeline, 111, 222, 333);
TimelineAsserts.assertPeriodCounts(timeline, 1, 1, 1); TimelineAsserts.assertPeriodCounts(timeline, 1, 1, 1);
for (boolean shuffled : new boolean[] { false, true }) { for (boolean shuffled : new boolean[] {false, true}) {
TimelineAsserts.assertPreviousWindowIndices(timeline, Player.REPEAT_MODE_OFF, shuffled, TimelineAsserts.assertPreviousWindowIndices(timeline, Player.REPEAT_MODE_OFF, shuffled,
2, 0, 1); 2, 0, 1);
TimelineAsserts.assertPreviousWindowIndices(timeline, Player.REPEAT_MODE_ONE, shuffled, TimelineAsserts.assertPreviousWindowIndices(timeline, Player.REPEAT_MODE_ONE, shuffled,
...@@ -93,6 +93,17 @@ public class LoopingMediaSourceTest extends TestCase { ...@@ -93,6 +93,17 @@ public class LoopingMediaSourceTest extends TestCase {
} }
} }
public void testEmptyTimelineLoop() {
Timeline timeline = getLoopingTimeline(Timeline.EMPTY, 1);
TimelineAsserts.assertEmpty(timeline);
timeline = getLoopingTimeline(Timeline.EMPTY, 3);
TimelineAsserts.assertEmpty(timeline);
timeline = getLoopingTimeline(Timeline.EMPTY, Integer.MAX_VALUE);
TimelineAsserts.assertEmpty(timeline);
}
/** /**
* Wraps the specified timeline in a {@link LoopingMediaSource} and returns * Wraps the specified timeline in a {@link LoopingMediaSource} and returns
* the looping timeline. * the looping timeline.
......
...@@ -606,10 +606,11 @@ public abstract class Timeline { ...@@ -606,10 +606,11 @@ public abstract class Timeline {
* enabled. * enabled.
* *
* @param shuffleModeEnabled Whether shuffling is enabled. * @param shuffleModeEnabled Whether shuffling is enabled.
* @return The index of the last window in the playback order. * @return The index of the last window in the playback order, or {@link C#INDEX_UNSET} if the
* timeline is empty.
*/ */
public int getLastWindowIndex(boolean shuffleModeEnabled) { public int getLastWindowIndex(boolean shuffleModeEnabled) {
return getWindowCount() - 1; return isEmpty() ? C.INDEX_UNSET : getWindowCount() - 1;
} }
/** /**
...@@ -617,10 +618,11 @@ public abstract class Timeline { ...@@ -617,10 +618,11 @@ public abstract class Timeline {
* enabled. * enabled.
* *
* @param shuffleModeEnabled Whether shuffling is enabled. * @param shuffleModeEnabled Whether shuffling is enabled.
* @return The index of the first window in the playback order. * @return The index of the first window in the playback order, or {@link C#INDEX_UNSET} if the
* timeline is empty.
*/ */
public int getFirstWindowIndex(boolean shuffleModeEnabled) { public int getFirstWindowIndex(boolean shuffleModeEnabled) {
return 0; return isEmpty() ? C.INDEX_UNSET : 0;
} }
/** /**
......
...@@ -42,6 +42,7 @@ import com.google.android.exoplayer2.Timeline; ...@@ -42,6 +42,7 @@ import com.google.android.exoplayer2.Timeline;
@Override @Override
public int getNextWindowIndex(int windowIndex, @Player.RepeatMode int repeatMode, public int getNextWindowIndex(int windowIndex, @Player.RepeatMode int repeatMode,
boolean shuffleModeEnabled) { boolean shuffleModeEnabled) {
// Find next window within current child.
int childIndex = getChildIndexByWindowIndex(windowIndex); int childIndex = getChildIndexByWindowIndex(windowIndex);
int firstWindowIndexInChild = getFirstWindowIndexByChildIndex(childIndex); int firstWindowIndexInChild = getFirstWindowIndexByChildIndex(childIndex);
int nextWindowIndexInChild = getTimelineByChildIndex(childIndex).getNextWindowIndex( int nextWindowIndexInChild = getTimelineByChildIndex(childIndex).getNextWindowIndex(
...@@ -51,12 +52,16 @@ import com.google.android.exoplayer2.Timeline; ...@@ -51,12 +52,16 @@ import com.google.android.exoplayer2.Timeline;
if (nextWindowIndexInChild != C.INDEX_UNSET) { if (nextWindowIndexInChild != C.INDEX_UNSET) {
return firstWindowIndexInChild + nextWindowIndexInChild; return firstWindowIndexInChild + nextWindowIndexInChild;
} }
int nextChildIndex = shuffleModeEnabled ? shuffleOrder.getNextIndex(childIndex) // If not found, find first window of next non-empty child.
: childIndex + 1; int nextChildIndex = getNextChildIndex(childIndex, shuffleModeEnabled);
if (nextChildIndex != C.INDEX_UNSET && nextChildIndex < childCount) { while (nextChildIndex != C.INDEX_UNSET && getTimelineByChildIndex(nextChildIndex).isEmpty()) {
nextChildIndex = getNextChildIndex(nextChildIndex, shuffleModeEnabled);
}
if (nextChildIndex != C.INDEX_UNSET) {
return getFirstWindowIndexByChildIndex(nextChildIndex) return getFirstWindowIndexByChildIndex(nextChildIndex)
+ getTimelineByChildIndex(nextChildIndex).getFirstWindowIndex(shuffleModeEnabled); + getTimelineByChildIndex(nextChildIndex).getFirstWindowIndex(shuffleModeEnabled);
} }
// If not found, this is the last window.
if (repeatMode == Player.REPEAT_MODE_ALL) { if (repeatMode == Player.REPEAT_MODE_ALL) {
return getFirstWindowIndex(shuffleModeEnabled); return getFirstWindowIndex(shuffleModeEnabled);
} }
...@@ -66,6 +71,7 @@ import com.google.android.exoplayer2.Timeline; ...@@ -66,6 +71,7 @@ import com.google.android.exoplayer2.Timeline;
@Override @Override
public int getPreviousWindowIndex(int windowIndex, @Player.RepeatMode int repeatMode, public int getPreviousWindowIndex(int windowIndex, @Player.RepeatMode int repeatMode,
boolean shuffleModeEnabled) { boolean shuffleModeEnabled) {
// Find previous window within current child.
int childIndex = getChildIndexByWindowIndex(windowIndex); int childIndex = getChildIndexByWindowIndex(windowIndex);
int firstWindowIndexInChild = getFirstWindowIndexByChildIndex(childIndex); int firstWindowIndexInChild = getFirstWindowIndexByChildIndex(childIndex);
int previousWindowIndexInChild = getTimelineByChildIndex(childIndex).getPreviousWindowIndex( int previousWindowIndexInChild = getTimelineByChildIndex(childIndex).getPreviousWindowIndex(
...@@ -75,12 +81,17 @@ import com.google.android.exoplayer2.Timeline; ...@@ -75,12 +81,17 @@ import com.google.android.exoplayer2.Timeline;
if (previousWindowIndexInChild != C.INDEX_UNSET) { if (previousWindowIndexInChild != C.INDEX_UNSET) {
return firstWindowIndexInChild + previousWindowIndexInChild; return firstWindowIndexInChild + previousWindowIndexInChild;
} }
int previousChildIndex = shuffleModeEnabled ? shuffleOrder.getPreviousIndex(childIndex) // If not found, find last window of previous non-empty child.
: childIndex - 1; int previousChildIndex = getPreviousChildIndex(childIndex, shuffleModeEnabled);
if (previousChildIndex != C.INDEX_UNSET && previousChildIndex >= 0) { while (previousChildIndex != C.INDEX_UNSET
&& getTimelineByChildIndex(previousChildIndex).isEmpty()) {
previousChildIndex = getPreviousChildIndex(previousChildIndex, shuffleModeEnabled);
}
if (previousChildIndex != C.INDEX_UNSET) {
return getFirstWindowIndexByChildIndex(previousChildIndex) return getFirstWindowIndexByChildIndex(previousChildIndex)
+ getTimelineByChildIndex(previousChildIndex).getLastWindowIndex(shuffleModeEnabled); + getTimelineByChildIndex(previousChildIndex).getLastWindowIndex(shuffleModeEnabled);
} }
// If not found, this is the first window.
if (repeatMode == Player.REPEAT_MODE_ALL) { if (repeatMode == Player.REPEAT_MODE_ALL) {
return getLastWindowIndex(shuffleModeEnabled); return getLastWindowIndex(shuffleModeEnabled);
} }
...@@ -89,14 +100,36 @@ import com.google.android.exoplayer2.Timeline; ...@@ -89,14 +100,36 @@ import com.google.android.exoplayer2.Timeline;
@Override @Override
public int getLastWindowIndex(boolean shuffleModeEnabled) { public int getLastWindowIndex(boolean shuffleModeEnabled) {
if (childCount == 0) {
return C.INDEX_UNSET;
}
// Find last non-empty child.
int lastChildIndex = shuffleModeEnabled ? shuffleOrder.getLastIndex() : childCount - 1; int lastChildIndex = shuffleModeEnabled ? shuffleOrder.getLastIndex() : childCount - 1;
while (getTimelineByChildIndex(lastChildIndex).isEmpty()) {
lastChildIndex = getPreviousChildIndex(lastChildIndex, shuffleModeEnabled);
if (lastChildIndex == C.INDEX_UNSET) {
// All children are empty.
return C.INDEX_UNSET;
}
}
return getFirstWindowIndexByChildIndex(lastChildIndex) return getFirstWindowIndexByChildIndex(lastChildIndex)
+ getTimelineByChildIndex(lastChildIndex).getLastWindowIndex(shuffleModeEnabled); + getTimelineByChildIndex(lastChildIndex).getLastWindowIndex(shuffleModeEnabled);
} }
@Override @Override
public int getFirstWindowIndex(boolean shuffleModeEnabled) { public int getFirstWindowIndex(boolean shuffleModeEnabled) {
if (childCount == 0) {
return C.INDEX_UNSET;
}
// Find first non-empty child.
int firstChildIndex = shuffleModeEnabled ? shuffleOrder.getFirstIndex() : 0; int firstChildIndex = shuffleModeEnabled ? shuffleOrder.getFirstIndex() : 0;
while (getTimelineByChildIndex(firstChildIndex).isEmpty()) {
firstChildIndex = getNextChildIndex(firstChildIndex, shuffleModeEnabled);
if (firstChildIndex == C.INDEX_UNSET) {
// All children are empty.
return C.INDEX_UNSET;
}
}
return getFirstWindowIndexByChildIndex(firstChildIndex) return getFirstWindowIndexByChildIndex(firstChildIndex)
+ getTimelineByChildIndex(firstChildIndex).getFirstWindowIndex(shuffleModeEnabled); + getTimelineByChildIndex(firstChildIndex).getFirstWindowIndex(shuffleModeEnabled);
} }
...@@ -196,4 +229,14 @@ import com.google.android.exoplayer2.Timeline; ...@@ -196,4 +229,14 @@ import com.google.android.exoplayer2.Timeline;
*/ */
protected abstract Object getChildUidByChildIndex(int childIndex); protected abstract Object getChildUidByChildIndex(int childIndex);
private int getNextChildIndex(int childIndex, boolean shuffleModeEnabled) {
return shuffleModeEnabled ? shuffleOrder.getNextIndex(childIndex)
: childIndex < childCount - 1 ? childIndex + 1 : C.INDEX_UNSET;
}
private int getPreviousChildIndex(int childIndex, boolean shuffleModeEnabled) {
return shuffleModeEnabled ? shuffleOrder.getPreviousIndex(childIndex)
: childIndex > 0 ? childIndex - 1 : C.INDEX_UNSET;
}
} }
...@@ -90,15 +90,19 @@ public final class ConcatenatingMediaSource implements MediaSource { ...@@ -90,15 +90,19 @@ public final class ConcatenatingMediaSource implements MediaSource {
@Override @Override
public void prepareSource(ExoPlayer player, boolean isTopLevelSource, Listener listener) { public void prepareSource(ExoPlayer player, boolean isTopLevelSource, Listener listener) {
this.listener = listener; this.listener = listener;
for (int i = 0; i < mediaSources.length; i++) { if (mediaSources.length == 0) {
if (!duplicateFlags[i]) { listener.onSourceInfoRefreshed(Timeline.EMPTY, null);
final int index = i; } else {
mediaSources[i].prepareSource(player, false, new Listener() { for (int i = 0; i < mediaSources.length; i++) {
@Override if (!duplicateFlags[i]) {
public void onSourceInfoRefreshed(Timeline timeline, Object manifest) { final int index = i;
handleSourceInfoRefreshed(index, timeline, manifest); mediaSources[i].prepareSource(player, false, new Listener() {
} @Override
}); public void onSourceInfoRefreshed(Timeline timeline, Object manifest) {
handleSourceInfoRefreshed(index, timeline, manifest);
}
});
}
} }
} }
} }
...@@ -245,12 +249,12 @@ public final class ConcatenatingMediaSource implements MediaSource { ...@@ -245,12 +249,12 @@ public final class ConcatenatingMediaSource implements MediaSource {
@Override @Override
protected int getChildIndexByPeriodIndex(int periodIndex) { protected int getChildIndexByPeriodIndex(int periodIndex) {
return Util.binarySearchFloor(sourcePeriodOffsets, periodIndex, true, false) + 1; return Util.binarySearchFloor(sourcePeriodOffsets, periodIndex + 1, false, false) + 1;
} }
@Override @Override
protected int getChildIndexByWindowIndex(int windowIndex) { protected int getChildIndexByWindowIndex(int windowIndex) {
return Util.binarySearchFloor(sourceWindowOffsets, windowIndex, true, false) + 1; return Util.binarySearchFloor(sourceWindowOffsets, windowIndex + 1, false, false) + 1;
} }
@Override @Override
......
...@@ -395,7 +395,14 @@ public final class DynamicConcatenatingMediaSource implements MediaSource, ExoPl ...@@ -395,7 +395,14 @@ public final class DynamicConcatenatingMediaSource implements MediaSource, ExoPl
private int findMediaSourceHolderByPeriodIndex(int periodIndex) { private int findMediaSourceHolderByPeriodIndex(int periodIndex) {
query.firstPeriodIndexInChild = periodIndex; query.firstPeriodIndexInChild = periodIndex;
int index = Collections.binarySearch(mediaSourceHolders, query); int index = Collections.binarySearch(mediaSourceHolders, query);
return index >= 0 ? index : -index - 2; if (index < 0) {
return -index - 2;
}
while (index < mediaSourceHolders.size() - 1
&& mediaSourceHolders.get(index + 1).firstPeriodIndexInChild == periodIndex) {
index++;
}
return index;
} }
private static final class MediaSourceHolder implements Comparable<MediaSourceHolder> { private static final class MediaSourceHolder implements Comparable<MediaSourceHolder> {
...@@ -456,12 +463,12 @@ public final class DynamicConcatenatingMediaSource implements MediaSource, ExoPl ...@@ -456,12 +463,12 @@ public final class DynamicConcatenatingMediaSource implements MediaSource, ExoPl
@Override @Override
protected int getChildIndexByPeriodIndex(int periodIndex) { protected int getChildIndexByPeriodIndex(int periodIndex) {
return Util.binarySearchFloor(firstPeriodInChildIndices, periodIndex, true, false); return Util.binarySearchFloor(firstPeriodInChildIndices, periodIndex + 1, false, false);
} }
@Override @Override
protected int getChildIndexByWindowIndex(int windowIndex) { protected int getChildIndexByWindowIndex(int windowIndex) {
return Util.binarySearchFloor(firstWindowInChildIndices, windowIndex, true, false); return Util.binarySearchFloor(firstWindowInChildIndices, windowIndex + 1, false, false);
} }
@Override @Override
......
...@@ -107,8 +107,10 @@ public final class LoopingMediaSource implements MediaSource { ...@@ -107,8 +107,10 @@ public final class LoopingMediaSource implements MediaSource {
childPeriodCount = childTimeline.getPeriodCount(); childPeriodCount = childTimeline.getPeriodCount();
childWindowCount = childTimeline.getWindowCount(); childWindowCount = childTimeline.getWindowCount();
this.loopCount = loopCount; this.loopCount = loopCount;
Assertions.checkState(loopCount <= Integer.MAX_VALUE / childPeriodCount, if (childPeriodCount > 0) {
"LoopingMediaSource contains too many periods"); Assertions.checkState(loopCount <= Integer.MAX_VALUE / childPeriodCount,
"LoopingMediaSource contains too many periods");
}
} }
@Override @Override
......
...@@ -36,6 +36,10 @@ public final class TimelineAsserts { ...@@ -36,6 +36,10 @@ public final class TimelineAsserts {
public static void assertEmpty(Timeline timeline) { public static void assertEmpty(Timeline timeline) {
assertWindowIds(timeline); assertWindowIds(timeline);
assertPeriodCounts(timeline); assertPeriodCounts(timeline);
for (boolean shuffled : new boolean[] {false, true}) {
assertEquals(C.INDEX_UNSET, timeline.getFirstWindowIndex(shuffled));
assertEquals(C.INDEX_UNSET, timeline.getLastWindowIndex(shuffled));
}
} }
/** /**
...@@ -119,8 +123,9 @@ public final class TimelineAsserts { ...@@ -119,8 +123,9 @@ public final class TimelineAsserts {
expectedWindowIndex++; expectedWindowIndex++;
} }
assertEquals(expectedWindowIndex, period.windowIndex); assertEquals(expectedWindowIndex, period.windowIndex);
assertEquals(i, timeline.getIndexOfPeriod(period.uid));
for (@Player.RepeatMode int repeatMode for (@Player.RepeatMode int repeatMode
: new int[] { Player.REPEAT_MODE_OFF, Player.REPEAT_MODE_ONE, Player.REPEAT_MODE_ALL }) { : new int[] {Player.REPEAT_MODE_OFF, Player.REPEAT_MODE_ONE, Player.REPEAT_MODE_ALL}) {
if (i < accumulatedPeriodCounts[expectedWindowIndex + 1] - 1) { if (i < accumulatedPeriodCounts[expectedWindowIndex + 1] - 1) {
assertEquals(i + 1, timeline.getNextPeriodIndex(i, period, window, repeatMode, false)); assertEquals(i + 1, timeline.getNextPeriodIndex(i, period, window, repeatMode, false));
} else { } else {
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or sign in to comment