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
This commit is contained in:
tonihei 2017-08-25 08:37:38 -07:00 committed by Oliver Woodman
parent 01f4819844
commit 30b31b5679
9 changed files with 275 additions and 36 deletions

View file

@ -31,11 +31,24 @@ import junit.framework.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() {
Timeline timeline = getConcatenatedTimeline(false, createFakeTimeline(3, 111));
TimelineAsserts.assertWindowIds(timeline, 111);
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,
C.INDEX_UNSET);
TimelineAsserts.assertPreviousWindowIndices(timeline, Player.REPEAT_MODE_ONE, shuffled, 0);
@ -49,7 +62,7 @@ public final class ConcatenatingMediaSourceTest extends TestCase {
timeline = getConcatenatedTimeline(true, createFakeTimeline(3, 111));
TimelineAsserts.assertWindowIds(timeline, 111);
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,
C.INDEX_UNSET);
TimelineAsserts.assertPreviousWindowIndices(timeline, Player.REPEAT_MODE_ONE, shuffled, 0);
@ -91,7 +104,7 @@ public final class ConcatenatingMediaSourceTest extends TestCase {
timeline = getConcatenatedTimeline(true, timelines);
TimelineAsserts.assertWindowIds(timeline, 111, 222, 333);
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,
C.INDEX_UNSET, 0, 1);
TimelineAsserts.assertPreviousWindowIndices(timeline, Player.REPEAT_MODE_ONE, shuffled,
@ -135,6 +148,54 @@ public final class ConcatenatingMediaSourceTest extends TestCase {
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
* the concatenated timeline.

View file

@ -15,6 +15,7 @@
*/
package com.google.android.exoplayer2.source;
import android.os.ConditionVariable;
import android.os.Handler;
import android.os.HandlerThread;
import android.os.Looper;
@ -25,7 +26,10 @@ import com.google.android.exoplayer2.ExoPlayer;
import com.google.android.exoplayer2.PlaybackParameters;
import com.google.android.exoplayer2.Player;
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.MediaPeriodId;
import com.google.android.exoplayer2.testutil.FakeMediaPeriod;
import com.google.android.exoplayer2.testutil.FakeMediaSource;
import com.google.android.exoplayer2.testutil.FakeShuffleOrder;
import com.google.android.exoplayer2.testutil.FakeTimeline;
@ -53,13 +57,13 @@ public final class DynamicConcatenatingMediaSourceTest extends TestCase {
DynamicConcatenatingMediaSource mediaSource = new DynamicConcatenatingMediaSource(
new FakeShuffleOrder(0));
prepareAndListenToTimelineUpdates(mediaSource);
assertNotNull(timeline);
waitForTimelineUpdate();
TimelineAsserts.assertEmpty(timeline);
// Add first source.
mediaSource.addMediaSource(childSources[0]);
waitForTimelineUpdate();
assertNotNull(timeline);
TimelineAsserts.assertPeriodCounts(timeline, 1);
TimelineAsserts.assertWindowIds(timeline, 111);
@ -143,6 +147,9 @@ public final class DynamicConcatenatingMediaSourceTest extends TestCase {
assertEquals(timeline.getWindowCount() - 1, timeline.getFirstWindowIndex(true));
assertEquals(0, timeline.getLastWindowIndex(true));
// Assert all periods can be prepared.
assertAllPeriodsCanBeCreatedPreparedAndReleased(mediaSource, timeline.getPeriodCount());
// Remove at front of queue.
mediaSource.removeMediaSource(0);
waitForTimelineUpdate();
@ -192,6 +199,7 @@ public final class DynamicConcatenatingMediaSourceTest extends TestCase {
TimelineAsserts.assertPreviousWindowIndices(timeline, Player.REPEAT_MODE_OFF, true,
1, 2, C.INDEX_UNSET);
assertAllPeriodsCanBeCreatedPreparedAndReleased(mediaSource, timeline.getPeriodCount());
mediaSource.releaseSource();
for (int i = 1; i < 4; i++) {
childSources[i].assertReleased();
@ -225,8 +233,9 @@ public final class DynamicConcatenatingMediaSourceTest extends TestCase {
TimelineAsserts.assertPeriodCounts(timeline, 1, 9);
TimelineAsserts.assertWindowIds(timeline, 111, 999);
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]);
waitForTimelineUpdate();
mediaSource.addMediaSource(2, childSources[1]);
@ -239,17 +248,90 @@ public final class DynamicConcatenatingMediaSourceTest extends TestCase {
TimelineAsserts.assertWindowIds(timeline, null, 111, 222, 999);
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));
waitForTimelineUpdate();
TimelineAsserts.assertPeriodCounts(timeline, 8, 1, 2, 9);
TimelineAsserts.assertWindowIds(timeline, 888, 111, 222, 999);
TimelineAsserts.assertWindowIsDynamic(timeline, false, false, false, false);
assertTrue(lazyPeriodPrepared.block(TIMEOUT_MS));
mediaSource.releasePeriod(lazyPeriod);
mediaSource.releaseSource();
childSources[0].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() {
DynamicConcatenatingMediaSource mediaSource = new DynamicConcatenatingMediaSource();
MediaSource validSource = new FakeMediaSource(createFakeTimeline(1), null);
@ -325,6 +407,28 @@ public final class DynamicConcatenatingMediaSourceTest extends TestCase {
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 Listener listener;
@ -344,7 +448,7 @@ public final class DynamicConcatenatingMediaSourceTest extends TestCase {
@Override
public MediaPeriod createPeriod(MediaPeriodId id, Allocator allocator) {
return null;
return new FakeMediaPeriod(TrackGroupArray.EMPTY);
}
@Override

View file

@ -42,7 +42,7 @@ public class LoopingMediaSourceTest extends TestCase {
Timeline timeline = getLoopingTimeline(multiWindowTimeline, 1);
TimelineAsserts.assertWindowIds(timeline, 111, 222, 333);
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,
C.INDEX_UNSET, 0, 1);
TimelineAsserts.assertPreviousWindowIndices(timeline, Player.REPEAT_MODE_ONE, shuffled,
@ -60,7 +60,7 @@ public class LoopingMediaSourceTest extends TestCase {
Timeline timeline = getLoopingTimeline(multiWindowTimeline, 3);
TimelineAsserts.assertWindowIds(timeline, 111, 222, 333, 111, 222, 333, 111, 222, 333);
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,
C.INDEX_UNSET, 0, 1, 2, 3, 4, 5, 6, 7, 8);
TimelineAsserts.assertPreviousWindowIndices(timeline, Player.REPEAT_MODE_ONE, shuffled,
@ -80,7 +80,7 @@ public class LoopingMediaSourceTest extends TestCase {
Timeline timeline = getLoopingTimeline(multiWindowTimeline, Integer.MAX_VALUE);
TimelineAsserts.assertWindowIds(timeline, 111, 222, 333);
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,
2, 0, 1);
TimelineAsserts.assertPreviousWindowIndices(timeline, Player.REPEAT_MODE_ONE, shuffled,
@ -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
* the looping timeline.

View file

@ -606,10 +606,11 @@ public abstract class Timeline {
* 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) {
return getWindowCount() - 1;
return isEmpty() ? C.INDEX_UNSET : getWindowCount() - 1;
}
/**
@ -617,10 +618,11 @@ public abstract class Timeline {
* 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) {
return 0;
return isEmpty() ? C.INDEX_UNSET : 0;
}
/**

View file

@ -42,6 +42,7 @@ import com.google.android.exoplayer2.Timeline;
@Override
public int getNextWindowIndex(int windowIndex, @Player.RepeatMode int repeatMode,
boolean shuffleModeEnabled) {
// Find next window within current child.
int childIndex = getChildIndexByWindowIndex(windowIndex);
int firstWindowIndexInChild = getFirstWindowIndexByChildIndex(childIndex);
int nextWindowIndexInChild = getTimelineByChildIndex(childIndex).getNextWindowIndex(
@ -51,12 +52,16 @@ import com.google.android.exoplayer2.Timeline;
if (nextWindowIndexInChild != C.INDEX_UNSET) {
return firstWindowIndexInChild + nextWindowIndexInChild;
}
int nextChildIndex = shuffleModeEnabled ? shuffleOrder.getNextIndex(childIndex)
: childIndex + 1;
if (nextChildIndex != C.INDEX_UNSET && nextChildIndex < childCount) {
// If not found, find first window of next non-empty child.
int nextChildIndex = getNextChildIndex(childIndex, shuffleModeEnabled);
while (nextChildIndex != C.INDEX_UNSET && getTimelineByChildIndex(nextChildIndex).isEmpty()) {
nextChildIndex = getNextChildIndex(nextChildIndex, shuffleModeEnabled);
}
if (nextChildIndex != C.INDEX_UNSET) {
return getFirstWindowIndexByChildIndex(nextChildIndex)
+ getTimelineByChildIndex(nextChildIndex).getFirstWindowIndex(shuffleModeEnabled);
}
// If not found, this is the last window.
if (repeatMode == Player.REPEAT_MODE_ALL) {
return getFirstWindowIndex(shuffleModeEnabled);
}
@ -66,6 +71,7 @@ import com.google.android.exoplayer2.Timeline;
@Override
public int getPreviousWindowIndex(int windowIndex, @Player.RepeatMode int repeatMode,
boolean shuffleModeEnabled) {
// Find previous window within current child.
int childIndex = getChildIndexByWindowIndex(windowIndex);
int firstWindowIndexInChild = getFirstWindowIndexByChildIndex(childIndex);
int previousWindowIndexInChild = getTimelineByChildIndex(childIndex).getPreviousWindowIndex(
@ -75,12 +81,17 @@ import com.google.android.exoplayer2.Timeline;
if (previousWindowIndexInChild != C.INDEX_UNSET) {
return firstWindowIndexInChild + previousWindowIndexInChild;
}
int previousChildIndex = shuffleModeEnabled ? shuffleOrder.getPreviousIndex(childIndex)
: childIndex - 1;
if (previousChildIndex != C.INDEX_UNSET && previousChildIndex >= 0) {
// If not found, find last window of previous non-empty child.
int previousChildIndex = getPreviousChildIndex(childIndex, shuffleModeEnabled);
while (previousChildIndex != C.INDEX_UNSET
&& getTimelineByChildIndex(previousChildIndex).isEmpty()) {
previousChildIndex = getPreviousChildIndex(previousChildIndex, shuffleModeEnabled);
}
if (previousChildIndex != C.INDEX_UNSET) {
return getFirstWindowIndexByChildIndex(previousChildIndex)
+ getTimelineByChildIndex(previousChildIndex).getLastWindowIndex(shuffleModeEnabled);
}
// If not found, this is the first window.
if (repeatMode == Player.REPEAT_MODE_ALL) {
return getLastWindowIndex(shuffleModeEnabled);
}
@ -89,14 +100,36 @@ import com.google.android.exoplayer2.Timeline;
@Override
public int getLastWindowIndex(boolean shuffleModeEnabled) {
if (childCount == 0) {
return C.INDEX_UNSET;
}
// Find last non-empty child.
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)
+ getTimelineByChildIndex(lastChildIndex).getLastWindowIndex(shuffleModeEnabled);
}
@Override
public int getFirstWindowIndex(boolean shuffleModeEnabled) {
if (childCount == 0) {
return C.INDEX_UNSET;
}
// Find first non-empty child.
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)
+ getTimelineByChildIndex(firstChildIndex).getFirstWindowIndex(shuffleModeEnabled);
}
@ -196,4 +229,14 @@ import com.google.android.exoplayer2.Timeline;
*/
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;
}
}

View file

@ -90,15 +90,19 @@ public final class ConcatenatingMediaSource implements MediaSource {
@Override
public void prepareSource(ExoPlayer player, boolean isTopLevelSource, Listener listener) {
this.listener = listener;
for (int i = 0; i < mediaSources.length; i++) {
if (!duplicateFlags[i]) {
final int index = i;
mediaSources[i].prepareSource(player, false, new Listener() {
@Override
public void onSourceInfoRefreshed(Timeline timeline, Object manifest) {
handleSourceInfoRefreshed(index, timeline, manifest);
}
});
if (mediaSources.length == 0) {
listener.onSourceInfoRefreshed(Timeline.EMPTY, null);
} else {
for (int i = 0; i < mediaSources.length; i++) {
if (!duplicateFlags[i]) {
final int index = i;
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 {
@Override
protected int getChildIndexByPeriodIndex(int periodIndex) {
return Util.binarySearchFloor(sourcePeriodOffsets, periodIndex, true, false) + 1;
return Util.binarySearchFloor(sourcePeriodOffsets, periodIndex + 1, false, false) + 1;
}
@Override
protected int getChildIndexByWindowIndex(int windowIndex) {
return Util.binarySearchFloor(sourceWindowOffsets, windowIndex, true, false) + 1;
return Util.binarySearchFloor(sourceWindowOffsets, windowIndex + 1, false, false) + 1;
}
@Override

View file

@ -395,7 +395,14 @@ public final class DynamicConcatenatingMediaSource implements MediaSource, ExoPl
private int findMediaSourceHolderByPeriodIndex(int periodIndex) {
query.firstPeriodIndexInChild = periodIndex;
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> {
@ -456,12 +463,12 @@ public final class DynamicConcatenatingMediaSource implements MediaSource, ExoPl
@Override
protected int getChildIndexByPeriodIndex(int periodIndex) {
return Util.binarySearchFloor(firstPeriodInChildIndices, periodIndex, true, false);
return Util.binarySearchFloor(firstPeriodInChildIndices, periodIndex + 1, false, false);
}
@Override
protected int getChildIndexByWindowIndex(int windowIndex) {
return Util.binarySearchFloor(firstWindowInChildIndices, windowIndex, true, false);
return Util.binarySearchFloor(firstWindowInChildIndices, windowIndex + 1, false, false);
}
@Override

View file

@ -107,8 +107,10 @@ public final class LoopingMediaSource implements MediaSource {
childPeriodCount = childTimeline.getPeriodCount();
childWindowCount = childTimeline.getWindowCount();
this.loopCount = loopCount;
Assertions.checkState(loopCount <= Integer.MAX_VALUE / childPeriodCount,
"LoopingMediaSource contains too many periods");
if (childPeriodCount > 0) {
Assertions.checkState(loopCount <= Integer.MAX_VALUE / childPeriodCount,
"LoopingMediaSource contains too many periods");
}
}
@Override

View file

@ -36,6 +36,10 @@ public final class TimelineAsserts {
public static void assertEmpty(Timeline timeline) {
assertWindowIds(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 {
expectedWindowIndex++;
}
assertEquals(expectedWindowIndex, period.windowIndex);
assertEquals(i, timeline.getIndexOfPeriod(period.uid));
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) {
assertEquals(i + 1, timeline.getNextPeriodIndex(i, period, window, repeatMode, false));
} else {