Add repeat mode support to Timeline implementations.

(Relating to GitHub Issue #2577)

The Timeline base class provides the default implementation.

Timeline wrappers (e.g. ClippingTimeline, ConcatatedTimeline) forward all
requests to the respective inner timelines. Some like ConcatenatedTimeline add
their own additional logic to bridge between the child timelines.

In addition, ConcatenatedTimeline and LoopingTimeline now have a common
abstract base class as they share most of their code.

-------------
Created by MOE: https://github.com/google/moe
MOE_MIGRATED_REVID=155509269
This commit is contained in:
tonihei 2017-05-09 08:53:29 -07:00 committed by Oliver Woodman
parent 4abf64b7ef
commit 8a210becef
5 changed files with 255 additions and 89 deletions

View file

@ -145,7 +145,16 @@ public abstract class Timeline {
* @return The index of the next window, or {@link C#INDEX_UNSET} if this is the last window.
*/
public int getNextWindowIndex(int windowIndex, @ExoPlayer.RepeatMode int repeatMode) {
return windowIndex == getWindowCount() - 1 ? C.INDEX_UNSET : windowIndex + 1;
switch (repeatMode) {
case ExoPlayer.REPEAT_MODE_OFF:
return windowIndex == getWindowCount() - 1 ? C.INDEX_UNSET : windowIndex + 1;
case ExoPlayer.REPEAT_MODE_ONE:
return windowIndex;
case ExoPlayer.REPEAT_MODE_ALL:
return windowIndex == getWindowCount() - 1 ? 0 : windowIndex + 1;
default:
throw new IllegalStateException();
}
}
/**
@ -157,7 +166,16 @@ public abstract class Timeline {
* @return The index of the previous window, or {@link C#INDEX_UNSET} if this is the first window.
*/
public int getPreviousWindowIndex(int windowIndex, @ExoPlayer.RepeatMode int repeatMode) {
return windowIndex == 0 ? C.INDEX_UNSET : windowIndex - 1;
switch (repeatMode) {
case ExoPlayer.REPEAT_MODE_OFF:
return windowIndex == 0 ? C.INDEX_UNSET : windowIndex - 1;
case ExoPlayer.REPEAT_MODE_ONE:
return windowIndex;
case ExoPlayer.REPEAT_MODE_ALL:
return windowIndex == 0 ? getWindowCount() - 1 : windowIndex - 1;
default:
throw new IllegalStateException();
}
}
/**

View file

@ -0,0 +1,145 @@
/*
* 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 android.util.Pair;
import com.google.android.exoplayer2.C;
import com.google.android.exoplayer2.ExoPlayer;
import com.google.android.exoplayer2.Timeline;
/**
* Abstract base class for the concatenation of one or more {@link Timeline}s.
*/
/* package */ abstract class AbstractConcatenatedTimeline extends Timeline {
@Override
public int getNextWindowIndex(int windowIndex, @ExoPlayer.RepeatMode int repeatMode) {
int childIndex = getChildIndexForWindow(windowIndex);
int firstWindowIndexInChild = getFirstWindowIndexInChild(childIndex);
int nextWindowIndexInChild = getChild(childIndex).getNextWindowIndex(
windowIndex - firstWindowIndexInChild,
repeatMode == ExoPlayer.REPEAT_MODE_ALL ? ExoPlayer.REPEAT_MODE_OFF : repeatMode);
if (nextWindowIndexInChild == C.INDEX_UNSET) {
if (childIndex < getChildCount() - 1) {
childIndex++;
} else if (repeatMode == ExoPlayer.REPEAT_MODE_ALL) {
childIndex = 0;
} else {
return C.INDEX_UNSET;
}
firstWindowIndexInChild = getFirstWindowIndexInChild(childIndex);
nextWindowIndexInChild = 0;
}
return firstWindowIndexInChild + nextWindowIndexInChild;
}
@Override
public int getPreviousWindowIndex(int windowIndex, @ExoPlayer.RepeatMode int repeatMode) {
int childIndex = getChildIndexForWindow(windowIndex);
int firstWindowIndexInChild = getFirstWindowIndexInChild(childIndex);
int previousWindowIndexInChild = getChild(childIndex).getPreviousWindowIndex(
windowIndex - firstWindowIndexInChild,
repeatMode == ExoPlayer.REPEAT_MODE_ALL ? ExoPlayer.REPEAT_MODE_OFF : repeatMode);
if (previousWindowIndexInChild == C.INDEX_UNSET) {
if (childIndex > 0) {
childIndex--;
} else if (repeatMode == ExoPlayer.REPEAT_MODE_ALL) {
childIndex = getChildCount() - 1;
} else {
return C.INDEX_UNSET;
}
firstWindowIndexInChild = getFirstWindowIndexInChild(childIndex);
previousWindowIndexInChild = getChild(childIndex).getWindowCount() - 1;
}
return firstWindowIndexInChild + previousWindowIndexInChild;
}
@Override
public final Window getWindow(int windowIndex, Window window, boolean setIds,
long defaultPositionProjectionUs) {
int childIndex = getChildIndexForWindow(windowIndex);
int firstWindowIndexInChild = getFirstWindowIndexInChild(childIndex);
int firstPeriodIndexInChild = getFirstPeriodIndexInChild(childIndex);
getChild(childIndex).getWindow(windowIndex - firstWindowIndexInChild, window, setIds,
defaultPositionProjectionUs);
window.firstPeriodIndex += firstPeriodIndexInChild;
window.lastPeriodIndex += firstPeriodIndexInChild;
return window;
}
@Override
public final Period getPeriod(int periodIndex, Period period, boolean setIds) {
int childIndex = getChildIndexForPeriod(periodIndex);
int firstWindowIndexInChild = getFirstWindowIndexInChild(childIndex);
int firstPeriodIndexInChild = getFirstPeriodIndexInChild(childIndex);
getChild(childIndex).getPeriod(periodIndex - firstPeriodIndexInChild, period, setIds);
period.windowIndex += firstWindowIndexInChild;
if (setIds) {
period.uid = Pair.create(childIndex, period.uid);
}
return period;
}
@Override
public final int getIndexOfPeriod(Object uid) {
if (!(uid instanceof Pair)) {
return C.INDEX_UNSET;
}
Pair<?, ?> childIndexAndPeriodId = (Pair<?, ?>) uid;
if (!(childIndexAndPeriodId.first instanceof Integer)) {
return C.INDEX_UNSET;
}
int childIndex = (Integer) childIndexAndPeriodId.first;
Object periodId = childIndexAndPeriodId.second;
if (childIndex < 0 || childIndex >= getChildCount()) {
return C.INDEX_UNSET;
}
int periodIndexInChild = getChild(childIndex).getIndexOfPeriod(periodId);
return periodIndexInChild == C.INDEX_UNSET ? C.INDEX_UNSET
: getFirstPeriodIndexInChild(childIndex) + periodIndexInChild;
}
/**
* Returns the number of concatenated child timelines.
*/
protected abstract int getChildCount();
/**
* Returns a child timeline by index.
*/
protected abstract Timeline getChild(int childIndex);
/**
* Returns the index of the child timeline to which the period with the given index belongs.
*/
protected abstract int getChildIndexForPeriod(int periodIndex);
/**
* Returns the first period index belonging to the child timeline with the given index.
*/
protected abstract int getFirstPeriodIndexInChild(int childIndex);
/**
* Returns the index of the child timeline to which the window with the given index belongs.
*/
protected abstract int getChildIndexForWindow(int windowIndex);
/**
* Returns the first window index belonging to the child timeline with the given index.
*/
protected abstract int getFirstWindowIndexInChild(int childIndex);
}

View file

@ -17,6 +17,7 @@ package com.google.android.exoplayer2.source;
import com.google.android.exoplayer2.C;
import com.google.android.exoplayer2.ExoPlayer;
import com.google.android.exoplayer2.ExoPlayer.RepeatMode;
import com.google.android.exoplayer2.Timeline;
import com.google.android.exoplayer2.upstream.Allocator;
import com.google.android.exoplayer2.util.Assertions;
@ -142,6 +143,16 @@ public final class ClippingMediaSource implements MediaSource, MediaSource.Liste
return 1;
}
@Override
public int getNextWindowIndex(int windowIndex, @RepeatMode int repeatMode) {
return timeline.getNextWindowIndex(windowIndex, repeatMode);
}
@Override
public int getPreviousWindowIndex(int windowIndex, @RepeatMode int repeatMode) {
return timeline.getPreviousWindowIndex(windowIndex, repeatMode);
}
@Override
public Window getWindow(int windowIndex, Window window, boolean setIds,
long defaultPositionProjectionUs) {

View file

@ -15,8 +15,6 @@
*/
package com.google.android.exoplayer2.source;
import android.util.Pair;
import com.google.android.exoplayer2.C;
import com.google.android.exoplayer2.ExoPlayer;
import com.google.android.exoplayer2.Timeline;
import com.google.android.exoplayer2.upstream.Allocator;
@ -38,6 +36,7 @@ public final class ConcatenatingMediaSource implements MediaSource {
private final Object[] manifests;
private final Map<MediaPeriod, Integer> sourceIndexByMediaPeriod;
private final boolean[] duplicateFlags;
private final boolean isRepeatOneAtomic;
private Listener listener;
private ConcatenatedTimeline timeline;
@ -47,7 +46,19 @@ public final class ConcatenatingMediaSource implements MediaSource {
* {@link MediaSource} instance to be present more than once in the array.
*/
public ConcatenatingMediaSource(MediaSource... mediaSources) {
this(false, mediaSources);
}
/**
* @param isRepeatOneAtomic Whether the concatenated media source shall be treated as atomic
* (i.e., repeated in its entirety) when repeat mode is set to
* {@code ExoPlayer.REPEAT_MODE_ONE}.
* @param mediaSources The {@link MediaSource}s to concatenate. It is valid for the same
* {@link MediaSource} instance to be present more than once in the array.
*/
public ConcatenatingMediaSource(boolean isRepeatOneAtomic, MediaSource... mediaSources) {
this.mediaSources = mediaSources;
this.isRepeatOneAtomic = isRepeatOneAtomic;
timelines = new Timeline[mediaSources.length];
manifests = new Object[mediaSources.length];
sourceIndexByMediaPeriod = new HashMap<>();
@ -81,8 +92,8 @@ public final class ConcatenatingMediaSource implements MediaSource {
@Override
public MediaPeriod createPeriod(int index, Allocator allocator, long positionUs) {
int sourceIndex = timeline.getSourceIndexForPeriod(index);
int periodIndexInSource = index - timeline.getFirstPeriodIndexInSource(sourceIndex);
int sourceIndex = timeline.getChildIndexForPeriod(index);
int periodIndexInSource = index - timeline.getFirstPeriodIndexInChild(sourceIndex);
MediaPeriod mediaPeriod = mediaSources[sourceIndex].createPeriod(periodIndexInSource, allocator,
positionUs);
sourceIndexByMediaPeriod.put(mediaPeriod, sourceIndex);
@ -123,7 +134,7 @@ public final class ConcatenatingMediaSource implements MediaSource {
return;
}
}
timeline = new ConcatenatedTimeline(timelines.clone());
timeline = new ConcatenatedTimeline(timelines.clone(), isRepeatOneAtomic);
listener.onSourceInfoRefreshed(timeline, manifests.clone());
}
@ -144,13 +155,14 @@ public final class ConcatenatingMediaSource implements MediaSource {
/**
* A {@link Timeline} that is the concatenation of one or more {@link Timeline}s.
*/
private static final class ConcatenatedTimeline extends Timeline {
private static final class ConcatenatedTimeline extends AbstractConcatenatedTimeline {
private final Timeline[] timelines;
private final int[] sourcePeriodOffsets;
private final int[] sourceWindowOffsets;
private final boolean isRepeatOneAtomic;
public ConcatenatedTimeline(Timeline[] timelines) {
public ConcatenatedTimeline(Timeline[] timelines, boolean isRepeatOneAtomic) {
int[] sourcePeriodOffsets = new int[timelines.length];
int[] sourceWindowOffsets = new int[timelines.length];
long periodCount = 0;
@ -167,6 +179,7 @@ public final class ConcatenatingMediaSource implements MediaSource {
this.timelines = timelines;
this.sourcePeriodOffsets = sourcePeriodOffsets;
this.sourceWindowOffsets = sourceWindowOffsets;
this.isRepeatOneAtomic = isRepeatOneAtomic;
}
@Override
@ -174,70 +187,55 @@ public final class ConcatenatingMediaSource implements MediaSource {
return sourceWindowOffsets[sourceWindowOffsets.length - 1];
}
@Override
public Window getWindow(int windowIndex, Window window, boolean setIds,
long defaultPositionProjectionUs) {
int sourceIndex = getSourceIndexForWindow(windowIndex);
int firstWindowIndexInSource = getFirstWindowIndexInSource(sourceIndex);
int firstPeriodIndexInSource = getFirstPeriodIndexInSource(sourceIndex);
timelines[sourceIndex].getWindow(windowIndex - firstWindowIndexInSource, window, setIds,
defaultPositionProjectionUs);
window.firstPeriodIndex += firstPeriodIndexInSource;
window.lastPeriodIndex += firstPeriodIndexInSource;
return window;
}
@Override
public int getPeriodCount() {
return sourcePeriodOffsets[sourcePeriodOffsets.length - 1];
}
@Override
public Period getPeriod(int periodIndex, Period period, boolean setIds) {
int sourceIndex = getSourceIndexForPeriod(periodIndex);
int firstWindowIndexInSource = getFirstWindowIndexInSource(sourceIndex);
int firstPeriodIndexInSource = getFirstPeriodIndexInSource(sourceIndex);
timelines[sourceIndex].getPeriod(periodIndex - firstPeriodIndexInSource, period, setIds);
period.windowIndex += firstWindowIndexInSource;
if (setIds) {
period.uid = Pair.create(sourceIndex, period.uid);
public int getNextWindowIndex(int windowIndex, @ExoPlayer.RepeatMode int repeatMode) {
if (isRepeatOneAtomic && repeatMode == ExoPlayer.REPEAT_MODE_ONE) {
repeatMode = ExoPlayer.REPEAT_MODE_ALL;
}
return period;
return super.getNextWindowIndex(windowIndex, repeatMode);
}
@Override
public int getIndexOfPeriod(Object uid) {
if (!(uid instanceof Pair)) {
return C.INDEX_UNSET;
public int getPreviousWindowIndex(int windowIndex, @ExoPlayer.RepeatMode int repeatMode) {
if (isRepeatOneAtomic && repeatMode == ExoPlayer.REPEAT_MODE_ONE) {
repeatMode = ExoPlayer.REPEAT_MODE_ALL;
}
Pair<?, ?> sourceIndexAndPeriodId = (Pair<?, ?>) uid;
if (!(sourceIndexAndPeriodId.first instanceof Integer)) {
return C.INDEX_UNSET;
}
int sourceIndex = (Integer) sourceIndexAndPeriodId.first;
Object periodId = sourceIndexAndPeriodId.second;
if (sourceIndex < 0 || sourceIndex >= timelines.length) {
return C.INDEX_UNSET;
}
int periodIndexInSource = timelines[sourceIndex].getIndexOfPeriod(periodId);
return periodIndexInSource == C.INDEX_UNSET ? C.INDEX_UNSET
: getFirstPeriodIndexInSource(sourceIndex) + periodIndexInSource;
return super.getPreviousWindowIndex(windowIndex, repeatMode);
}
private int getSourceIndexForPeriod(int periodIndex) {
@Override
protected int getChildCount() {
return timelines.length;
}
@Override
protected Timeline getChild(int childIndex) {
return timelines[childIndex];
}
@Override
protected int getChildIndexForPeriod(int periodIndex) {
return Util.binarySearchFloor(sourcePeriodOffsets, periodIndex, true, false) + 1;
}
private int getFirstPeriodIndexInSource(int sourceIndex) {
return sourceIndex == 0 ? 0 : sourcePeriodOffsets[sourceIndex - 1];
@Override
protected int getFirstPeriodIndexInChild(int childIndex) {
return childIndex == 0 ? 0 : sourcePeriodOffsets[childIndex - 1];
}
private int getSourceIndexForWindow(int windowIndex) {
@Override
protected int getChildIndexForWindow(int windowIndex) {
return Util.binarySearchFloor(sourceWindowOffsets, windowIndex, true, false) + 1;
}
private int getFirstWindowIndexInSource(int sourceIndex) {
return sourceIndex == 0 ? 0 : sourceWindowOffsets[sourceIndex - 1];
@Override
protected int getFirstWindowIndexInChild(int childIndex) {
return childIndex == 0 ? 0 : sourceWindowOffsets[childIndex - 1];
}
}

View file

@ -15,7 +15,6 @@
*/
package com.google.android.exoplayer2.source;
import android.util.Pair;
import com.google.android.exoplayer2.C;
import com.google.android.exoplayer2.ExoPlayer;
import com.google.android.exoplayer2.Timeline;
@ -91,7 +90,7 @@ public final class LoopingMediaSource implements MediaSource {
childSource.releaseSource();
}
private static final class LoopingTimeline extends Timeline {
private static final class LoopingTimeline extends AbstractConcatenatedTimeline {
private final Timeline childTimeline;
private final int childPeriodCount;
@ -112,47 +111,40 @@ public final class LoopingMediaSource implements MediaSource {
return childWindowCount * loopCount;
}
@Override
public Window getWindow(int windowIndex, Window window, boolean setIds,
long defaultPositionProjectionUs) {
childTimeline.getWindow(windowIndex % childWindowCount, window, setIds,
defaultPositionProjectionUs);
int periodIndexOffset = (windowIndex / childWindowCount) * childPeriodCount;
window.firstPeriodIndex += periodIndexOffset;
window.lastPeriodIndex += periodIndexOffset;
return window;
}
@Override
public int getPeriodCount() {
return childPeriodCount * loopCount;
}
@Override
public Period getPeriod(int periodIndex, Period period, boolean setIds) {
childTimeline.getPeriod(periodIndex % childPeriodCount, period, setIds);
int loopCount = (periodIndex / childPeriodCount);
period.windowIndex += loopCount * childWindowCount;
if (setIds) {
period.uid = Pair.create(loopCount, period.uid);
}
return period;
protected Timeline getChild(int childIndex) {
return childTimeline;
}
@Override
public int getIndexOfPeriod(Object uid) {
if (!(uid instanceof Pair)) {
return C.INDEX_UNSET;
}
Pair<?, ?> loopCountAndChildUid = (Pair<?, ?>) uid;
if (!(loopCountAndChildUid.first instanceof Integer)) {
return C.INDEX_UNSET;
}
int loopCount = (Integer) loopCountAndChildUid.first;
int periodIndexOffset = loopCount * childPeriodCount;
return childTimeline.getIndexOfPeriod(loopCountAndChildUid.second) + periodIndexOffset;
protected int getChildCount() {
return loopCount;
}
@Override
protected int getChildIndexForPeriod(int periodIndex) {
return periodIndex / childPeriodCount;
}
@Override
protected int getFirstPeriodIndexInChild(int childIndex) {
return childIndex * childPeriodCount;
}
@Override
protected int getChildIndexForWindow(int windowIndex) {
return windowIndex / childWindowCount;
}
@Override
protected int getFirstWindowIndexInChild(int childIndex) {
return childIndex * childWindowCount;
}
}
private static final class InfinitelyLoopingTimeline extends Timeline {
@ -169,14 +161,16 @@ public final class LoopingMediaSource implements MediaSource {
}
@Override
public int getNextWindowIndex(int currentWindowIndex, @ExoPlayer.RepeatMode int repeatMode) {
return currentWindowIndex < getWindowCount() - 1 ? currentWindowIndex + 1 : 0;
public int getNextWindowIndex(int windowIndex, @ExoPlayer.RepeatMode int repeatMode) {
int childNextWindowIndex = childTimeline.getNextWindowIndex(windowIndex, repeatMode);
return childNextWindowIndex == C.INDEX_UNSET ? 0 : childNextWindowIndex;
}
@Override
public int getPreviousWindowIndex(int currentWindowIndex,
@ExoPlayer.RepeatMode int repeatMode) {
return currentWindowIndex > 0 ? currentWindowIndex - 1 : getWindowCount() - 1;
public int getPreviousWindowIndex(int windowIndex, @ExoPlayer.RepeatMode int repeatMode) {
int childPreviousWindowIndex = childTimeline.getPreviousWindowIndex(windowIndex, repeatMode);
return childPreviousWindowIndex == C.INDEX_UNSET ? getWindowCount() - 1
: childPreviousWindowIndex;
}
@Override