Support isAtomic flag in DynamicConcatenatingMediaSource.

This feature is supported in the ConcatenatingMediaSource and is easily copied to this
media source. Also adding tests to check whether the atomic property works in normal
concatenation and in also in nested use.

Also fixes a bug where timeline methods of the DeferredTimeline were not correctly
forwarded.

-------------
Created by MOE: https://github.com/google/moe
MOE_MIGRATED_REVID=184526881
This commit is contained in:
tonihei 2018-02-05 07:34:31 -08:00 committed by Oliver Woodman
parent fe98477045
commit 784e8a6344
5 changed files with 197 additions and 80 deletions

View file

@ -47,7 +47,8 @@ public final class DynamicConcatenatingMediaSourceTest extends TestCase {
@Override
public void setUp() throws Exception {
super.setUp();
mediaSource = new DynamicConcatenatingMediaSource(new FakeShuffleOrder(0));
mediaSource =
new DynamicConcatenatingMediaSource(/* isAtomic= */ false, new FakeShuffleOrder(0));
testRunner = new MediaSourceTestRunner(mediaSource, null);
}
@ -627,6 +628,92 @@ public final class DynamicConcatenatingMediaSourceTest extends TestCase {
mediaSourceWithAds.assertMediaPeriodCreated(new MediaPeriodId(1, 0, 0));
}
public void testAtomicTimelineWindowOrder() throws IOException {
// Release default test runner with non-atomic media source and replace with new test runner.
testRunner.release();
DynamicConcatenatingMediaSource mediaSource =
new DynamicConcatenatingMediaSource(/* isAtomic= */ true, new FakeShuffleOrder(0));
testRunner = new MediaSourceTestRunner(mediaSource, null);
mediaSource.addMediaSources(Arrays.<MediaSource>asList(createMediaSources(3)));
Timeline timeline = testRunner.prepareSource();
TimelineAsserts.assertWindowIds(timeline, 111, 222, 333);
TimelineAsserts.assertPeriodCounts(timeline, 1, 2, 3);
TimelineAsserts.assertPreviousWindowIndices(
timeline, Player.REPEAT_MODE_OFF, /* shuffleModeEnabled= */ false, C.INDEX_UNSET, 0, 1);
TimelineAsserts.assertPreviousWindowIndices(
timeline, Player.REPEAT_MODE_OFF, /* shuffleModeEnabled= */ true, C.INDEX_UNSET, 0, 1);
TimelineAsserts.assertPreviousWindowIndices(
timeline, Player.REPEAT_MODE_ONE, /* shuffleModeEnabled= */ false, 2, 0, 1);
TimelineAsserts.assertPreviousWindowIndices(
timeline, Player.REPEAT_MODE_ONE, /* shuffleModeEnabled= */ true, 2, 0, 1);
TimelineAsserts.assertPreviousWindowIndices(
timeline, Player.REPEAT_MODE_ALL, /* shuffleModeEnabled= */ false, 2, 0, 1);
TimelineAsserts.assertPreviousWindowIndices(
timeline, Player.REPEAT_MODE_ALL, /* shuffleModeEnabled= */ true, 2, 0, 1);
TimelineAsserts.assertNextWindowIndices(
timeline, Player.REPEAT_MODE_OFF, /* shuffleModeEnabled= */ false, 1, 2, C.INDEX_UNSET);
TimelineAsserts.assertNextWindowIndices(
timeline, Player.REPEAT_MODE_OFF, /* shuffleModeEnabled= */ true, 1, 2, C.INDEX_UNSET);
TimelineAsserts.assertNextWindowIndices(
timeline, Player.REPEAT_MODE_ONE, /* shuffleModeEnabled= */ false, 1, 2, 0);
TimelineAsserts.assertNextWindowIndices(
timeline, Player.REPEAT_MODE_ONE, /* shuffleModeEnabled= */ true, 1, 2, 0);
TimelineAsserts.assertNextWindowIndices(
timeline, Player.REPEAT_MODE_ALL, /* shuffleModeEnabled= */ false, 1, 2, 0);
TimelineAsserts.assertNextWindowIndices(
timeline, Player.REPEAT_MODE_ALL, /* shuffleModeEnabled= */ true, 1, 2, 0);
assertThat(timeline.getFirstWindowIndex(/* shuffleModeEnabled= */ false)).isEqualTo(0);
assertThat(timeline.getFirstWindowIndex(/* shuffleModeEnabled= */ true)).isEqualTo(0);
assertThat(timeline.getLastWindowIndex(/* shuffleModeEnabled= */ false)).isEqualTo(2);
assertThat(timeline.getLastWindowIndex(/* shuffleModeEnabled= */ true)).isEqualTo(2);
}
public void testNestedTimeline() throws IOException {
DynamicConcatenatingMediaSource nestedSource1 =
new DynamicConcatenatingMediaSource(/* isAtomic= */ false, new FakeShuffleOrder(0));
DynamicConcatenatingMediaSource nestedSource2 =
new DynamicConcatenatingMediaSource(/* isAtomic= */ true, new FakeShuffleOrder(0));
mediaSource.addMediaSource(nestedSource1);
mediaSource.addMediaSource(nestedSource2);
testRunner.prepareSource();
FakeMediaSource[] childSources = createMediaSources(4);
nestedSource1.addMediaSource(childSources[0]);
testRunner.assertTimelineChangeBlocking();
nestedSource1.addMediaSource(childSources[1]);
testRunner.assertTimelineChangeBlocking();
nestedSource2.addMediaSource(childSources[2]);
testRunner.assertTimelineChangeBlocking();
nestedSource2.addMediaSource(childSources[3]);
Timeline timeline = testRunner.assertTimelineChangeBlocking();
TimelineAsserts.assertWindowIds(timeline, 111, 222, 333, 444);
TimelineAsserts.assertPeriodCounts(timeline, 1, 2, 3, 4);
TimelineAsserts.assertPreviousWindowIndices(
timeline, Player.REPEAT_MODE_OFF, /* shuffleModeEnabled= */ false, C.INDEX_UNSET, 0, 1, 2);
TimelineAsserts.assertPreviousWindowIndices(
timeline, Player.REPEAT_MODE_ONE, /* shuffleModeEnabled= */ false, 0, 1, 3, 2);
TimelineAsserts.assertPreviousWindowIndices(
timeline, Player.REPEAT_MODE_ALL, /* shuffleModeEnabled= */ false, 3, 0, 1, 2);
TimelineAsserts.assertNextWindowIndices(
timeline, Player.REPEAT_MODE_OFF, /* shuffleModeEnabled= */ false, 1, 2, 3, C.INDEX_UNSET);
TimelineAsserts.assertNextWindowIndices(
timeline, Player.REPEAT_MODE_ONE, /* shuffleModeEnabled= */ false, 0, 1, 3, 2);
TimelineAsserts.assertNextWindowIndices(
timeline, Player.REPEAT_MODE_ALL, /* shuffleModeEnabled= */ false, 1, 2, 3, 0);
TimelineAsserts.assertPreviousWindowIndices(
timeline, Player.REPEAT_MODE_OFF, /* shuffleModeEnabled= */ true, 1, 3, C.INDEX_UNSET, 2);
TimelineAsserts.assertPreviousWindowIndices(
timeline, Player.REPEAT_MODE_ONE, /* shuffleModeEnabled= */ true, 0, 1, 3, 2);
TimelineAsserts.assertPreviousWindowIndices(
timeline, Player.REPEAT_MODE_ALL, /* shuffleModeEnabled= */ true, 1, 3, 0, 2);
TimelineAsserts.assertNextWindowIndices(
timeline, Player.REPEAT_MODE_OFF, /* shuffleModeEnabled= */ true, C.INDEX_UNSET, 0, 3, 1);
TimelineAsserts.assertNextWindowIndices(
timeline, Player.REPEAT_MODE_ONE, /* shuffleModeEnabled= */ true, 0, 1, 3, 2);
TimelineAsserts.assertNextWindowIndices(
timeline, Player.REPEAT_MODE_ALL, /* shuffleModeEnabled= */ true, 2, 0, 3, 1);
}
public void testRemoveChildSourceWithActiveMediaPeriod() throws IOException {
FakeMediaSource childSource = createFakeMediaSource();
mediaSource.addMediaSource(childSource);

View file

@ -27,14 +27,18 @@ import com.google.android.exoplayer2.Timeline;
private final int childCount;
private final ShuffleOrder shuffleOrder;
private final boolean isAtomic;
/**
* Sets up a concatenated timeline with a shuffle order of child timelines.
*
* @param isAtomic Whether the child timelines shall be treated as atomic, i.e., treated as a
* single item for repeating and shuffling.
* @param shuffleOrder A shuffle order of child timelines. The number of child timelines must
* match the number of elements in the shuffle order.
*/
public AbstractConcatenatedTimeline(ShuffleOrder shuffleOrder) {
public AbstractConcatenatedTimeline(boolean isAtomic, ShuffleOrder shuffleOrder) {
this.isAtomic = isAtomic;
this.shuffleOrder = shuffleOrder;
this.childCount = shuffleOrder.getLength();
}
@ -42,6 +46,11 @@ import com.google.android.exoplayer2.Timeline;
@Override
public int getNextWindowIndex(int windowIndex, @Player.RepeatMode int repeatMode,
boolean shuffleModeEnabled) {
if (isAtomic) {
// Adapt repeat and shuffle mode to atomic concatenation.
repeatMode = repeatMode == Player.REPEAT_MODE_ONE ? Player.REPEAT_MODE_ALL : repeatMode;
shuffleModeEnabled = false;
}
// Find next window within current child.
int childIndex = getChildIndexByWindowIndex(windowIndex);
int firstWindowIndexInChild = getFirstWindowIndexByChildIndex(childIndex);
@ -71,6 +80,11 @@ import com.google.android.exoplayer2.Timeline;
@Override
public int getPreviousWindowIndex(int windowIndex, @Player.RepeatMode int repeatMode,
boolean shuffleModeEnabled) {
if (isAtomic) {
// Adapt repeat and shuffle mode to atomic concatenation.
repeatMode = repeatMode == Player.REPEAT_MODE_ONE ? Player.REPEAT_MODE_ALL : repeatMode;
shuffleModeEnabled = false;
}
// Find previous window within current child.
int childIndex = getChildIndexByWindowIndex(windowIndex);
int firstWindowIndexInChild = getFirstWindowIndexByChildIndex(childIndex);
@ -103,6 +117,9 @@ import com.google.android.exoplayer2.Timeline;
if (childCount == 0) {
return C.INDEX_UNSET;
}
if (isAtomic) {
shuffleModeEnabled = false;
}
// Find last non-empty child.
int lastChildIndex = shuffleModeEnabled ? shuffleOrder.getLastIndex() : childCount - 1;
while (getTimelineByChildIndex(lastChildIndex).isEmpty()) {
@ -121,6 +138,9 @@ import com.google.android.exoplayer2.Timeline;
if (childCount == 0) {
return C.INDEX_UNSET;
}
if (isAtomic) {
shuffleModeEnabled = false;
}
// Find first non-empty child.
int firstChildIndex = shuffleModeEnabled ? shuffleOrder.getFirstIndex() : 0;
while (getTimelineByChildIndex(firstChildIndex).isEmpty()) {

View file

@ -18,7 +18,6 @@ package com.google.android.exoplayer2.source;
import android.support.annotation.Nullable;
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.DefaultShuffleOrder;
import com.google.android.exoplayer2.upstream.Allocator;
@ -173,10 +172,9 @@ public final class ConcatenatingMediaSource extends CompositeMediaSource<Integer
private final Timeline[] timelines;
private final int[] sourcePeriodOffsets;
private final int[] sourceWindowOffsets;
private final boolean isAtomic;
public ConcatenatedTimeline(Timeline[] timelines, boolean isAtomic, ShuffleOrder shuffleOrder) {
super(shuffleOrder);
super(isAtomic, shuffleOrder);
int[] sourcePeriodOffsets = new int[timelines.length];
int[] sourceWindowOffsets = new int[timelines.length];
long periodCount = 0;
@ -193,7 +191,6 @@ public final class ConcatenatingMediaSource extends CompositeMediaSource<Integer
this.timelines = timelines;
this.sourcePeriodOffsets = sourcePeriodOffsets;
this.sourceWindowOffsets = sourceWindowOffsets;
this.isAtomic = isAtomic;
}
@Override
@ -206,34 +203,6 @@ public final class ConcatenatingMediaSource extends CompositeMediaSource<Integer
return sourcePeriodOffsets[sourcePeriodOffsets.length - 1];
}
@Override
public int getNextWindowIndex(int windowIndex, @Player.RepeatMode int repeatMode,
boolean shuffleModeEnabled) {
if (isAtomic && repeatMode == Player.REPEAT_MODE_ONE) {
repeatMode = Player.REPEAT_MODE_ALL;
}
return super.getNextWindowIndex(windowIndex, repeatMode, !isAtomic && shuffleModeEnabled);
}
@Override
public int getPreviousWindowIndex(int windowIndex, @Player.RepeatMode int repeatMode,
boolean shuffleModeEnabled) {
if (isAtomic && repeatMode == Player.REPEAT_MODE_ONE) {
repeatMode = Player.REPEAT_MODE_ALL;
}
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
protected int getChildIndexByPeriodIndex(int periodIndex) {
return Util.binarySearchFloor(sourcePeriodOffsets, periodIndex + 1, false, false) + 1;

View file

@ -58,6 +58,7 @@ public final class DynamicConcatenatingMediaSource extends CompositeMediaSource<
private final MediaSourceHolder query;
private final Map<MediaPeriod, MediaSourceHolder> mediaSourceByMediaPeriod;
private final List<DeferredMediaPeriod> deferredMediaPeriods;
private final boolean isAtomic;
private ExoPlayer player;
private Listener listener;
@ -70,22 +71,35 @@ public final class DynamicConcatenatingMediaSource extends CompositeMediaSource<
* Creates a new dynamic concatenating media source.
*/
public DynamicConcatenatingMediaSource() {
this(new DefaultShuffleOrder(0));
this(/* isAtomic= */ false, new DefaultShuffleOrder(0));
}
/**
* Creates a new dynamic concatenating media source.
*
* @param isAtomic Whether the concatenating media source will be treated as atomic, i.e., treated
* as a single item for repeating and shuffling.
*/
public DynamicConcatenatingMediaSource(boolean isAtomic) {
this(isAtomic, new DefaultShuffleOrder(0));
}
/**
* Creates a new dynamic concatenating media source with a custom shuffle order.
*
* @param isAtomic Whether the concatenating media source will 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.
* This shuffle order must be empty.
*/
public DynamicConcatenatingMediaSource(ShuffleOrder shuffleOrder) {
public DynamicConcatenatingMediaSource(boolean isAtomic, ShuffleOrder shuffleOrder) {
this.shuffleOrder = shuffleOrder;
this.mediaSourceByMediaPeriod = new IdentityHashMap<>();
this.mediaSourcesPublic = new ArrayList<>();
this.mediaSourceHolders = new ArrayList<>();
this.deferredMediaPeriods = new ArrayList<>(1);
this.query = new MediaSourceHolder(null, null, -1, -1, -1);
this.isAtomic = isAtomic;
}
/**
@ -446,8 +460,10 @@ public final class DynamicConcatenatingMediaSource extends CompositeMediaSource<
private void maybeNotifyListener(@Nullable EventDispatcher actionOnCompletion) {
if (!preventListenerNotification) {
listener.onSourceInfoRefreshed(this,
new ConcatenatedTimeline(mediaSourceHolders, windowCount, periodCount, shuffleOrder),
listener.onSourceInfoRefreshed(
this,
new ConcatenatedTimeline(
mediaSourceHolders, windowCount, periodCount, shuffleOrder, isAtomic),
null);
if (actionOnCompletion != null) {
player.createMessage(this).setType(MSG_ON_COMPLETION).setPayload(actionOnCompletion).send();
@ -652,9 +668,13 @@ public final class DynamicConcatenatingMediaSource extends CompositeMediaSource<
private final int[] uids;
private final SparseIntArray childIndexByUid;
public ConcatenatedTimeline(Collection<MediaSourceHolder> mediaSourceHolders, int windowCount,
int periodCount, ShuffleOrder shuffleOrder) {
super(shuffleOrder);
public ConcatenatedTimeline(
Collection<MediaSourceHolder> mediaSourceHolders,
int windowCount,
int periodCount,
ShuffleOrder shuffleOrder,
boolean isAtomic) {
super(isAtomic, shuffleOrder);
this.windowCount = windowCount;
this.periodCount = periodCount;
int childCount = mediaSourceHolders.size();
@ -728,61 +748,39 @@ public final class DynamicConcatenatingMediaSource extends CompositeMediaSource<
* Timeline used as placeholder for an unprepared media source. After preparation, a copy of the
* DeferredTimeline is used to keep the originally assigned first period ID.
*/
private static final class DeferredTimeline extends Timeline {
private static final class DeferredTimeline extends ForwardingTimeline {
private static final Object DUMMY_ID = new Object();
private static final Period period = new Period();
private static final DummyTimeline dummyTimeline = new DummyTimeline();
private final Timeline timeline;
private final Object replacedID;
private final Object replacedId;
public DeferredTimeline() {
timeline = null;
replacedID = null;
this(dummyTimeline, /* replacedId= */ null);
}
private DeferredTimeline(Timeline timeline, Object replacedID) {
this.timeline = timeline;
this.replacedID = replacedID;
private DeferredTimeline(Timeline timeline, Object replacedId) {
super(timeline);
this.replacedId = replacedId;
}
public DeferredTimeline cloneWithNewTimeline(Timeline timeline) {
return new DeferredTimeline(timeline, replacedID == null && timeline.getPeriodCount() > 0
? timeline.getPeriod(0, period, true).uid : replacedID);
return new DeferredTimeline(
timeline,
replacedId == null && timeline.getPeriodCount() > 0
? timeline.getPeriod(0, period, true).uid
: replacedId);
}
public Timeline getTimeline() {
return timeline;
}
@Override
public int getWindowCount() {
return timeline == null ? 1 : timeline.getWindowCount();
}
@Override
public Window getWindow(int windowIndex, Window window, boolean setIds,
long defaultPositionProjectionUs) {
return timeline == null
// Dynamic window to indicate pending timeline updates.
? window.set(setIds ? DUMMY_ID : null, C.TIME_UNSET, C.TIME_UNSET, false, true, 0,
C.TIME_UNSET, 0, 0, 0)
: timeline.getWindow(windowIndex, window, setIds, defaultPositionProjectionUs);
}
@Override
public int getPeriodCount() {
return timeline == null ? 1 : timeline.getPeriodCount();
}
@Override
public Period getPeriod(int periodIndex, Period period, boolean setIds) {
if (timeline == null) {
return period.set(setIds ? DUMMY_ID : null, setIds ? DUMMY_ID : null, 0, C.TIME_UNSET,
C.TIME_UNSET);
}
timeline.getPeriod(periodIndex, period, setIds);
if (period.uid == replacedID) {
if (period.uid == replacedId) {
period.uid = DUMMY_ID;
}
return period;
@ -790,11 +788,54 @@ public final class DynamicConcatenatingMediaSource extends CompositeMediaSource<
@Override
public int getIndexOfPeriod(Object uid) {
return timeline == null ? (uid == DUMMY_ID ? 0 : C.INDEX_UNSET)
: timeline.getIndexOfPeriod(uid == DUMMY_ID ? replacedID : uid);
return timeline.getIndexOfPeriod(uid == DUMMY_ID ? replacedId : uid);
}
}
/** Dummy placeholder timeline with one dynamic window with a period of indeterminate duration. */
private static final class DummyTimeline extends Timeline {
@Override
public int getWindowCount() {
return 1;
}
@Override
public Window getWindow(int windowIndex, Window window, boolean setIds,
long defaultPositionProjectionUs) {
// Dynamic window to indicate pending timeline updates.
return window.set(
/* id= */ null,
/* presentationStartTimeMs= */ C.TIME_UNSET,
/* windowStartTimeMs= */ C.TIME_UNSET,
/* isSeekable= */ false,
/* isDynamic= */ true,
/* defaultPositionUs= */ 0,
/* durationUs= */ C.TIME_UNSET,
/* firstPeriodIndex= */ 0,
/* lastPeriodIndex= */ 0,
/* positionInFirstPeriodUs= */ 0);
}
@Override
public int getPeriodCount() {
return 1;
}
@Override
public Period getPeriod(int periodIndex, Period period, boolean setIds) {
return period.set(
/* id= */ null,
/* uid= */ null,
/* windowIndex= */ 0,
/* durationUs = */ C.TIME_UNSET,
/* positionInWindowUs= */ C.TIME_UNSET);
}
@Override
public int getIndexOfPeriod(Object uid) {
return uid == null ? 0 : C.INDEX_UNSET;
}
}
}

View file

@ -106,7 +106,7 @@ public final class LoopingMediaSource extends CompositeMediaSource<Void> {
private final int loopCount;
public LoopingTimeline(Timeline childTimeline, int loopCount) {
super(new UnshuffledShuffleOrder(loopCount));
super(/* isAtomic= */ false, new UnshuffledShuffleOrder(loopCount));
this.childTimeline = childTimeline;
childPeriodCount = childTimeline.getPeriodCount();
childWindowCount = childTimeline.getWindowCount();