diff --git a/library/src/main/java/com/google/android/exoplayer2/Timeline.java b/library/src/main/java/com/google/android/exoplayer2/Timeline.java index b46c3a6149..1cc2773ad5 100644 --- a/library/src/main/java/com/google/android/exoplayer2/Timeline.java +++ b/library/src/main/java/com/google/android/exoplayer2/Timeline.java @@ -15,8 +15,6 @@ */ package com.google.android.exoplayer2; -import com.google.android.exoplayer2.source.MediaSource.Listener; - /** * The player's timeline consisting of one or more periods. Instances are immutable. */ @@ -44,26 +42,27 @@ public interface Timeline { long getAbsoluteStartTime(); /** - * Returns the duration of the period at {@code index} in the timeline, in milliseconds, or + * Returns the duration of the period at {@code periodIndex} in the timeline, in milliseconds, or * {@link ExoPlayer#UNKNOWN_TIME} if not known. * - * @param index The index of the period. + * @param periodIndex The index of the period. * @return The duration of the period in milliseconds, or {@link ExoPlayer#UNKNOWN_TIME}. */ - long getPeriodDurationMs(int index); + long getPeriodDurationMs(int periodIndex); /** - * Returns the duration of the period at {@code index} in the timeline, in microseconds, or + * Returns the duration of the period at {@code periodIndex} in the timeline, in microseconds, or * {@link C#UNSET_TIME_US} if not known. * - * @param index The index of the period. + * @param periodIndex The index of the period. * @return The duration of the period in microseconds, or {@link C#UNSET_TIME_US}. */ - long getPeriodDurationUs(int index); + long getPeriodDurationUs(int periodIndex); /** - * Returns a unique identifier for the period at {@code index}, or {@code null} if the period at - * {@code index} is not known. The identifier is stable across {@link Timeline} instances. + * Returns a unique identifier for the period at {@code periodIndex}, or {@code null} if the + * period at {@code periodIndex} is not known. The identifier is stable across {@link Timeline} + * instances. *

* When the timeline changes the player uses period identifiers to determine what periods are * unchanged. Implementations that associate an object with each period can return the object for @@ -71,10 +70,18 @@ public interface Timeline { * identifiers that can't clash with (for example) identifiers used by other timelines that may be * concatenated with this one. * - * @param index A period index. + * @param periodIndex A period index. * @return An identifier for the period, or {@code null} if the period is not known. */ - Object getPeriodId(int index); + Object getPeriodId(int periodIndex); + + /** + * Returns the index of the window to which the period with the specified index belongs. + * + * @param periodIndex The period index. + * @return The index of the corresponding seek window. + */ + int getPeriodWindowIndex(int periodIndex); /** * Returns the index of the period identified by {@code id}, or {@link #NO_PERIOD_INDEX} if the @@ -91,10 +98,10 @@ public interface Timeline { int getWindowCount(); /** - * Returns the {@link Window} at {@code index}, which represents positions that can be seeked - * to in the timeline. The windows may change when - * {@link Listener#onSourceInfoRefreshed(Timeline, Object)} is called. + * Returns the {@link Window} at the specified index. + * + * @param windowIndex The window index. */ - Window getWindow(int index); + Window getWindow(int windowIndex); } diff --git a/library/src/main/java/com/google/android/exoplayer2/source/ConcatenatingMediaSource.java b/library/src/main/java/com/google/android/exoplayer2/source/ConcatenatingMediaSource.java index a54475e011..66d3011f06 100644 --- a/library/src/main/java/com/google/android/exoplayer2/source/ConcatenatingMediaSource.java +++ b/library/src/main/java/com/google/android/exoplayer2/source/ConcatenatingMediaSource.java @@ -131,37 +131,38 @@ public final class ConcatenatingMediaSource implements MediaSource { private final Timeline[] timelines; private final boolean isFinal; - private final int[] sourceOffsets; + private final int[] sourcePeriodOffsets; + private final int[] sourceWindowOffsets; private final Window[] windows; public ConcatenatedTimeline(Timeline[] timelines) { boolean isFinal = true; - int[] sourceOffsets = new int[timelines.length]; - int sourceOffset = 0; + int[] sourcePeriodOffsets = new int[timelines.length]; + int[] sourceWindowOffsets = new int[timelines.length]; + int periodCount = 0; + int windowCount = 0; ArrayList concatenatedWindows = new ArrayList<>(); for (int i = 0; i < timelines.length; i++) { Timeline timeline = timelines[i]; isFinal &= timeline.isFinal(); // Offset the windows so they are relative to the source. - int windowCount = timeline.getWindowCount(); - for (int j = 0; j < windowCount; j++) { + int sourceWindowCount = timeline.getWindowCount(); + for (int j = 0; j < sourceWindowCount; j++) { Window sourceWindow = timeline.getWindow(j); - concatenatedWindows.add(sourceWindow.copyOffsetByPeriodCount(sourceOffset)); + concatenatedWindows.add(sourceWindow.copyOffsetByPeriodCount(periodCount)); } - sourceOffset += timeline.getPeriodCount(); - sourceOffsets[i] = sourceOffset; + periodCount += timeline.getPeriodCount(); + sourcePeriodOffsets[i] = periodCount; + windowCount += timeline.getWindowCount(); + sourceWindowOffsets[i] = windowCount; } this.timelines = timelines; this.isFinal = isFinal; - this.sourceOffsets = sourceOffsets; + this.sourcePeriodOffsets = sourcePeriodOffsets; + this.sourceWindowOffsets = sourceWindowOffsets; windows = concatenatedWindows.toArray(new Window[concatenatedWindows.size()]); } - @Override - public int getPeriodCount() { - return sourceOffsets[sourceOffsets.length - 1]; - } - @Override public boolean isFinal() { return isFinal; @@ -173,27 +174,40 @@ public final class ConcatenatingMediaSource implements MediaSource { } @Override - public long getPeriodDurationMs(int index) { - int sourceIndex = getSourceIndexForPeriod(index); - int firstPeriodIndexInSource = getFirstPeriodIndexInSource(sourceIndex); - return timelines[sourceIndex].getPeriodDurationMs(index - firstPeriodIndexInSource); + public int getPeriodCount() { + return sourcePeriodOffsets[sourcePeriodOffsets.length - 1]; } @Override - public long getPeriodDurationUs(int index) { - int sourceIndex = getSourceIndexForPeriod(index); + public long getPeriodDurationMs(int periodIndex) { + int sourceIndex = getSourceIndexForPeriod(periodIndex); int firstPeriodIndexInSource = getFirstPeriodIndexInSource(sourceIndex); - return timelines[sourceIndex].getPeriodDurationUs(index - firstPeriodIndexInSource); + return timelines[sourceIndex].getPeriodDurationMs(periodIndex - firstPeriodIndexInSource); } @Override - public Object getPeriodId(int index) { - int sourceIndex = getSourceIndexForPeriod(index); - int firstPeriodIndexInSource = getFirstPeriodIndexInSource(index); - Object periodId = timelines[sourceIndex].getPeriodId(index - firstPeriodIndexInSource); + public long getPeriodDurationUs(int periodIndex) { + int sourceIndex = getSourceIndexForPeriod(periodIndex); + int firstPeriodIndexInSource = getFirstPeriodIndexInSource(sourceIndex); + return timelines[sourceIndex].getPeriodDurationUs(periodIndex - firstPeriodIndexInSource); + } + + @Override + public Object getPeriodId(int periodIndex) { + int sourceIndex = getSourceIndexForPeriod(periodIndex); + int firstPeriodIndexInSource = getFirstPeriodIndexInSource(periodIndex); + Object periodId = timelines[sourceIndex].getPeriodId(periodIndex - firstPeriodIndexInSource); return Pair.create(sourceIndex, periodId); } + @Override + public int getPeriodWindowIndex(int periodIndex) { + int sourceIndex = getSourceIndexForPeriod(periodIndex); + int firstPeriodIndexInSource = getFirstPeriodIndexInSource(periodIndex); + return (sourceIndex > 0 ? sourceWindowOffsets[sourceIndex - 1] : 0) + + timelines[sourceIndex].getPeriodWindowIndex(periodIndex - firstPeriodIndexInSource); + } + @Override public int getIndexOfPeriod(Object id) { // The id was returned by getPeriodId, so it is always a Pair. @@ -215,16 +229,16 @@ public final class ConcatenatingMediaSource implements MediaSource { } @Override - public Window getWindow(int index) { - return windows[index]; + public Window getWindow(int windowIndex) { + return windows[windowIndex]; } private int getSourceIndexForPeriod(int periodIndex) { - return Util.binarySearchFloor(sourceOffsets, periodIndex, true, false) + 1; + return Util.binarySearchFloor(sourcePeriodOffsets, periodIndex, true, false) + 1; } private int getFirstPeriodIndexInSource(int sourceIndex) { - return sourceIndex == 0 ? 0 : sourceOffsets[sourceIndex - 1]; + return sourceIndex == 0 ? 0 : sourcePeriodOffsets[sourceIndex - 1]; } } diff --git a/library/src/main/java/com/google/android/exoplayer2/source/SinglePeriodTimeline.java b/library/src/main/java/com/google/android/exoplayer2/source/SinglePeriodTimeline.java index 46da9ebfbf..3544468810 100644 --- a/library/src/main/java/com/google/android/exoplayer2/source/SinglePeriodTimeline.java +++ b/library/src/main/java/com/google/android/exoplayer2/source/SinglePeriodTimeline.java @@ -58,11 +58,6 @@ public final class SinglePeriodTimeline implements Timeline { this.isFinal = true; // TODO: Remove. } - @Override - public int getPeriodCount() { - return 1; - } - @Override public boolean isFinal() { return isFinal; @@ -74,29 +69,34 @@ public final class SinglePeriodTimeline implements Timeline { } @Override - public long getPeriodDurationMs(int index) { - if (index != 0) { - throw new IndexOutOfBoundsException(); - } + public int getPeriodCount() { + return 1; + } + + @Override + public long getPeriodDurationMs(int periodIndex) { + Assertions.checkIndex(periodIndex, 0, 1); return durationUs == C.UNSET_TIME_US ? ExoPlayer.UNKNOWN_TIME : (durationUs / 1000); } @Override - public long getPeriodDurationUs(int index) { - if (index != 0) { - throw new IndexOutOfBoundsException(); - } + public long getPeriodDurationUs(int periodIndex) { + Assertions.checkIndex(periodIndex, 0, 1); return durationUs; } @Override - public Object getPeriodId(int index) { - if (index != 0) { - throw new IndexOutOfBoundsException(); - } + public Object getPeriodId(int periodIndex) { + Assertions.checkIndex(periodIndex, 0, 1); return id; } + @Override + public int getPeriodWindowIndex(int periodIndex) { + Assertions.checkIndex(periodIndex, 0, 1); + return 0; + } + @Override public int getIndexOfPeriod(Object id) { return id.equals(this.id) ? 0 : Timeline.NO_PERIOD_INDEX; @@ -108,7 +108,8 @@ public final class SinglePeriodTimeline implements Timeline { } @Override - public Window getWindow(int index) { + public Window getWindow(int windowIndex) { + Assertions.checkIndex(windowIndex, 0, 1); return window; } diff --git a/library/src/main/java/com/google/android/exoplayer2/source/dash/DashMediaSource.java b/library/src/main/java/com/google/android/exoplayer2/source/dash/DashMediaSource.java index 291267da03..13bf9f305b 100644 --- a/library/src/main/java/com/google/android/exoplayer2/source/dash/DashMediaSource.java +++ b/library/src/main/java/com/google/android/exoplayer2/source/dash/DashMediaSource.java @@ -37,6 +37,7 @@ import com.google.android.exoplayer2.upstream.Allocator; import com.google.android.exoplayer2.upstream.DataSource; import com.google.android.exoplayer2.upstream.Loader; import com.google.android.exoplayer2.upstream.ParsingLoadable; +import com.google.android.exoplayer2.util.Assertions; import com.google.android.exoplayer2.util.Util; import java.io.BufferedReader; import java.io.IOException; @@ -498,11 +499,6 @@ public final class DashMediaSource implements MediaSource { this.window = window; } - @Override - public int getPeriodCount() { - return manifest.getPeriodCount(); - } - @Override public boolean isFinal() { return !manifest.dynamic; @@ -514,24 +510,31 @@ public final class DashMediaSource implements MediaSource { } @Override - public long getPeriodDurationMs(int index) { - if (index < 0 || index >= manifest.getPeriodCount()) { - throw new IndexOutOfBoundsException(); - } - return manifest.getPeriodDurationMs(index); + public int getPeriodCount() { + return manifest.getPeriodCount(); } @Override - public long getPeriodDurationUs(int index) { - if (index < 0 || index >= manifest.getPeriodCount()) { - throw new IndexOutOfBoundsException(); - } - return manifest.getPeriodDurationUs(index); + public long getPeriodDurationMs(int periodIndex) { + Assertions.checkIndex(periodIndex, 0, manifest.getPeriodCount()); + return manifest.getPeriodDurationMs(periodIndex); } @Override - public Object getPeriodId(int index) { - return firstPeriodId + index; + public long getPeriodDurationUs(int periodIndex) { + Assertions.checkIndex(periodIndex, 0, manifest.getPeriodCount()); + return manifest.getPeriodDurationUs(periodIndex); + } + + @Override + public Object getPeriodId(int periodIndex) { + return firstPeriodId + Assertions.checkIndex(periodIndex, 0, manifest.getPeriodCount()); + } + + @Override + public int getPeriodWindowIndex(int periodIndex) { + Assertions.checkIndex(periodIndex, 0, manifest.getPeriodCount()); + return 0; } @Override @@ -545,7 +548,8 @@ public final class DashMediaSource implements MediaSource { } @Override - public Window getWindow(int index) { + public Window getWindow(int windowIndex) { + Assertions.checkIndex(windowIndex, 0, 1); return window; } diff --git a/library/src/main/java/com/google/android/exoplayer2/util/Assertions.java b/library/src/main/java/com/google/android/exoplayer2/util/Assertions.java index 237e16192f..dccfb32203 100644 --- a/library/src/main/java/com/google/android/exoplayer2/util/Assertions.java +++ b/library/src/main/java/com/google/android/exoplayer2/util/Assertions.java @@ -27,10 +27,9 @@ public final class Assertions { private Assertions() {} /** - * Ensures the truth of an expression involving one or more arguments passed to the calling - * method. + * Throws {@link IllegalArgumentException} if {@code expression} evaluates to false. * - * @param expression A boolean expression. + * @param expression The expression to evaluate. * @throws IllegalArgumentException If {@code expression} is false. */ public static void checkArgument(boolean expression) { @@ -40,11 +39,10 @@ public final class Assertions { } /** - * Ensures the truth of an expression involving one or more arguments passed to the calling - * method. + * Throws {@link IllegalArgumentException} if {@code expression} evaluates to false. * - * @param expression A boolean expression. - * @param errorMessage The exception message to use if the check fails. The message is converted + * @param expression The expression to evaluate. + * @param errorMessage The exception message if an exception is thrown. The message is converted * to a {@link String} using {@link String#valueOf(Object)}. * @throws IllegalArgumentException If {@code expression} is false. */ @@ -55,9 +53,25 @@ public final class Assertions { } /** - * Ensures the truth of an expression involving the state of the calling instance. + * Throws {@link IndexOutOfBoundsException} if {@code index} falls outside the specified bounds. * - * @param expression A boolean expression. + * @param index The index to test. + * @param start The start of the allowed range (inclusive). + * @param limit The end of the allowed range (exclusive). + * @return The {@code index} that was validated. + * @throws IndexOutOfBoundsException If {@code index} falls outside the specified bounds. + */ + public static int checkIndex(int index, int start, int limit) { + if (index < start || index >= limit) { + throw new IndexOutOfBoundsException(); + } + return index; + } + + /** + * Throws {@link IllegalStateException} if {@code expression} evaluates to false. + * + * @param expression The expression to evaluate. * @throws IllegalStateException If {@code expression} is false. */ public static void checkState(boolean expression) { @@ -67,11 +81,11 @@ public final class Assertions { } /** - * Ensures the truth of an expression involving the state of the calling instance. + * Throws {@link IllegalStateException} if {@code expression} evaluates to false. * - * @param expression A boolean expression. - * @param errorMessage The exception message to use if the check fails. The message is converted - * to a string using {@link String#valueOf(Object)}. + * @param expression The expression to evaluate. + * @param errorMessage The exception message if an exception is thrown. The message is converted + * to a {@link String} using {@link String#valueOf(Object)}. * @throws IllegalStateException If {@code expression} is false. */ public static void checkState(boolean expression, Object errorMessage) { @@ -81,7 +95,7 @@ public final class Assertions { } /** - * Ensures that an object reference is not null. + * Throws {@link NullPointerException} if {@code reference} is null. * * @param reference An object reference. * @return The non-null reference that was validated. @@ -95,7 +109,7 @@ public final class Assertions { } /** - * Ensures that an object reference is not null. + * Throws {@link NullPointerException} if {@code reference} is null. * * @param reference An object reference. * @param errorMessage The exception message to use if the check fails. The message is converted @@ -111,9 +125,9 @@ public final class Assertions { } /** - * Ensures that a string passed as an argument to the calling method is not null or 0-length. + * Throws {@link IllegalArgumentException} if {@code string} is null or zero length. * - * @param string A string. + * @param string The string to check. * @return The non-null, non-empty string that was validated. * @throws IllegalArgumentException If {@code string} is null or 0-length. */ @@ -125,9 +139,9 @@ public final class Assertions { } /** - * Ensures that a string passed as an argument to the calling method is not null or 0-length. + * Throws {@link IllegalArgumentException} if {@code string} is null or zero length. * - * @param string A string. + * @param string The string to check. * @param errorMessage The exception message to use if the check fails. The message is converted * to a string using {@link String#valueOf(Object)}. * @return The non-null, non-empty string that was validated. @@ -141,7 +155,8 @@ public final class Assertions { } /** - * Ensures that the calling thread is the application's main thread. + * Throws {@link IllegalStateException} if the calling thread is not the application's main + * thread. * * @throws IllegalStateException If the calling thread is not the application's main thread. */