diff --git a/library/core/src/main/java/com/google/android/exoplayer2/Timeline.java b/library/core/src/main/java/com/google/android/exoplayer2/Timeline.java index 8dc30b0905..1a33985c68 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/Timeline.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/Timeline.java @@ -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(); + } } /** diff --git a/library/core/src/main/java/com/google/android/exoplayer2/source/AbstractConcatenatedTimeline.java b/library/core/src/main/java/com/google/android/exoplayer2/source/AbstractConcatenatedTimeline.java new file mode 100644 index 0000000000..37672703d7 --- /dev/null +++ b/library/core/src/main/java/com/google/android/exoplayer2/source/AbstractConcatenatedTimeline.java @@ -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); + +} diff --git a/library/core/src/main/java/com/google/android/exoplayer2/source/ClippingMediaSource.java b/library/core/src/main/java/com/google/android/exoplayer2/source/ClippingMediaSource.java index be15a07726..c61dea9553 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/source/ClippingMediaSource.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/source/ClippingMediaSource.java @@ -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) { diff --git a/library/core/src/main/java/com/google/android/exoplayer2/source/ConcatenatingMediaSource.java b/library/core/src/main/java/com/google/android/exoplayer2/source/ConcatenatingMediaSource.java index 9fc499f251..2299e757d7 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/source/ConcatenatingMediaSource.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/source/ConcatenatingMediaSource.java @@ -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 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]; } } diff --git a/library/core/src/main/java/com/google/android/exoplayer2/source/LoopingMediaSource.java b/library/core/src/main/java/com/google/android/exoplayer2/source/LoopingMediaSource.java index 0c872f199c..0e1e7d9033 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/source/LoopingMediaSource.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/source/LoopingMediaSource.java @@ -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