diff --git a/library/src/main/java/com/google/android/exoplayer2/ExoPlayerImpl.java b/library/src/main/java/com/google/android/exoplayer2/ExoPlayerImpl.java index e83e335f09..3e3b1f8fa7 100644 --- a/library/src/main/java/com/google/android/exoplayer2/ExoPlayerImpl.java +++ b/library/src/main/java/com/google/android/exoplayer2/ExoPlayerImpl.java @@ -161,8 +161,7 @@ import java.util.concurrent.CopyOnWriteArraySet; throw new IllegalArgumentException("Windows are not yet known"); } Assertions.checkIndex(windowIndex, 0, timeline.getWindowCount()); - Window window = timeline.getWindow(windowIndex); - seekToDefaultPositionForPeriod(window.startPeriodIndex); + seekToDefaultPositionForPeriod(timeline.getWindowFirstPeriodIndex(windowIndex)); } @Override @@ -181,12 +180,14 @@ import java.util.concurrent.CopyOnWriteArraySet; throw new IllegalArgumentException("Windows are not yet known"); } Assertions.checkIndex(windowIndex, 0, timeline.getWindowCount()); - Window window = timeline.getWindow(windowIndex); - int periodIndex = window.startPeriodIndex; - long periodPositionMs = window.startTimeMs + positionMs; + int firstPeriodIndex = timeline.getWindowFirstPeriodIndex(windowIndex); + int lastPeriodIndex = timeline.getWindowLastPeriodIndex(windowIndex); + int periodIndex = firstPeriodIndex; + long periodPositionMs = timeline.getWindowOffsetInFirstPeriodUs(windowIndex) / 1000 + + positionMs; long periodDurationMs = timeline.getPeriodDurationMs(periodIndex); while (periodDurationMs != UNKNOWN_TIME && periodPositionMs >= periodDurationMs - && periodIndex < window.endPeriodIndex) { + && periodIndex < lastPeriodIndex) { periodPositionMs -= periodDurationMs; periodDurationMs = timeline.getPeriodDurationMs(++periodIndex); } @@ -272,7 +273,8 @@ import java.util.concurrent.CopyOnWriteArraySet; if (timeline == null) { return UNKNOWN_TIME; } - return timeline.getWindow(getCurrentWindowIndex()).durationMs; + long durationUs = timeline.getWindow(getCurrentWindowIndex()).durationUs; + return durationUs == C.UNSET_TIME_US ? ExoPlayer.UNKNOWN_TIME : durationUs / 1000; } @Override @@ -282,13 +284,12 @@ import java.util.concurrent.CopyOnWriteArraySet; } int periodIndex = getCurrentPeriodIndex(); int windowIndex = timeline.getPeriodWindowIndex(periodIndex); - Window window = timeline.getWindow(windowIndex); - long position = getCurrentPositionInPeriod(); - for (int i = window.startPeriodIndex; i < periodIndex; i++) { - position += timeline.getPeriodDurationMs(i); + long positionMs = getCurrentPositionInPeriod(); + for (int i = timeline.getWindowFirstPeriodIndex(windowIndex); i < periodIndex; i++) { + positionMs += timeline.getPeriodDurationMs(i); } - position -= window.startTimeMs; - return position; + positionMs -= timeline.getWindowOffsetInFirstPeriodUs(windowIndex) / 1000; + return positionMs; } @Override @@ -300,8 +301,11 @@ import java.util.concurrent.CopyOnWriteArraySet; int periodIndex = getCurrentPeriodIndex(); int windowIndex = timeline.getPeriodWindowIndex(periodIndex); Window window = timeline.getWindow(windowIndex); - if (window.startPeriodIndex == periodIndex && window.endPeriodIndex == periodIndex - && window.startTimeMs == 0 && window.durationMs == getCurrentPeriodDuration()) { + int firstPeriodIndex = timeline.getWindowFirstPeriodIndex(windowIndex); + int lastPeriodIndex = timeline.getWindowLastPeriodIndex(windowIndex); + if (firstPeriodIndex == periodIndex && lastPeriodIndex == periodIndex + && timeline.getWindowOffsetInFirstPeriodUs(windowIndex) == 0 + && window.durationUs == timeline.getPeriodDurationUs(periodIndex)) { return getBufferedPositionInPeriod(); } return getCurrentPosition(); diff --git a/library/src/main/java/com/google/android/exoplayer2/ExoPlayerImplInternal.java b/library/src/main/java/com/google/android/exoplayer2/ExoPlayerImplInternal.java index cfd82968f3..3b7b107e2b 100644 --- a/library/src/main/java/com/google/android/exoplayer2/ExoPlayerImplInternal.java +++ b/library/src/main/java/com/google/android/exoplayer2/ExoPlayerImplInternal.java @@ -498,9 +498,9 @@ import java.io.IOException; if (positionUs == C.UNSET_TIME_US && timeline != null && periodIndex < timeline.getPeriodCount()) { // We know about the window, so seek to its default initial position now. - Window window = timeline.getPeriodWindow(periodIndex); - periodIndex = window.defaultInitialPeriodIndex; - positionUs = window.defaultInitialTimeMs * 1000; + Pair defaultPosition = getDefaultPosition(periodIndex); + periodIndex = defaultPosition.first; + positionUs = defaultPosition.second; } if (periodIndex == playbackInfo.periodIndex @@ -795,28 +795,7 @@ import java.io.IOException; if (playingPeriod != null) { int index = timeline.getIndexOfPeriod(playingPeriod.id); if (index == Timeline.NO_PERIOD_INDEX) { - int newPlayingPeriodIndex = - mediaSource.getNewPlayingPeriodIndex(playingPeriod.index, oldTimeline); - if (newPlayingPeriodIndex == Timeline.NO_PERIOD_INDEX) { - // There is no period to play, so stop the player. - stopInternal(); - return; - } - - // Release all loaded periods. - releasePeriodsFrom(playingPeriod); - playingPeriod = null; - readingPeriod = null; - loadingPeriod = null; - - // Find the default initial position in the window and seek to it. - Window window = timeline.getPeriodWindow(newPlayingPeriodIndex); - newPlayingPeriodIndex = window.defaultInitialPeriodIndex; - long newPlayingPositionUs = seekToPeriodPosition(newPlayingPeriodIndex, - window.defaultInitialTimeMs); - - playbackInfo = new PlaybackInfo(newPlayingPeriodIndex, newPlayingPositionUs); - eventHandler.obtainMessage(MSG_POSITION_DISCONTINUITY, playbackInfo).sendToTarget(); + attemptRestart(timeline, oldTimeline, playingPeriod.index); return; } @@ -867,9 +846,8 @@ import java.io.IOException; Object id = loadingPeriod.id; int index = timeline.getIndexOfPeriod(id); if (index == Timeline.NO_PERIOD_INDEX) { - loadingPeriod.release(); - loadingPeriod = null; - bufferAheadPeriodCount = 0; + attemptRestart(timeline, oldTimeline, loadingPeriod.index); + return; } else { loadingPeriod.setIndex(timeline, index); } @@ -888,6 +866,48 @@ import java.io.IOException; } } + private void attemptRestart(Timeline newTimeline, Timeline oldTimeline, int oldPeriodIndex) + throws ExoPlaybackException { + int newPeriodIndex = Timeline.NO_PERIOD_INDEX; + while (newPeriodIndex == Timeline.NO_PERIOD_INDEX + && oldPeriodIndex < oldTimeline.getPeriodCount() - 1) { + newPeriodIndex = newTimeline.getIndexOfPeriod(oldTimeline.getPeriodId(++oldPeriodIndex)); + } + if (newPeriodIndex == Timeline.NO_PERIOD_INDEX) { + // We failed to find a replacement period. Stop the player. + stopInternal(); + return; + } + + // Release all loaded periods. + releasePeriodsFrom(playingPeriod); + bufferAheadPeriodCount = 0; + playingPeriod = null; + readingPeriod = null; + loadingPeriod = null; + + // Find the default initial position in the window and seek to it. + Pair defaultPosition = getDefaultPosition(newPeriodIndex); + newPeriodIndex = defaultPosition.first; + long newPlayingPositionUs = defaultPosition.second; + + playbackInfo = new PlaybackInfo(newPeriodIndex, newPlayingPositionUs); + eventHandler.obtainMessage(MSG_POSITION_DISCONTINUITY, playbackInfo).sendToTarget(); + } + + private Pair getDefaultPosition(int periodIndex) { + int windowIndex = timeline.getPeriodWindowIndex(periodIndex); + periodIndex = timeline.getWindowFirstPeriodIndex(windowIndex); + int maxPeriodIndex = timeline.getWindowLastPeriodIndex(windowIndex); + long windowPositionUs = timeline.getWindowOffsetInFirstPeriodUs(windowIndex) + + timeline.getWindow(windowIndex).defaultStartPositionUs; + while (periodIndex < maxPeriodIndex + && windowPositionUs > timeline.getPeriodDurationUs(periodIndex)) { + windowPositionUs -= timeline.getPeriodDurationUs(periodIndex++); + } + return Pair.create(periodIndex, windowPositionUs); + } + private void updatePeriods() throws ExoPlaybackException, IOException { if (timeline == null) { // We're waiting to get information about periods. @@ -895,7 +915,6 @@ import java.io.IOException; return; } - if (loadingPeriod == null || (loadingPeriod.isFullyBuffered() && !loadingPeriod.isLast && bufferAheadPeriodCount < MAXIMUM_BUFFER_AHEAD_PERIODS)) { // We don't have a loading period or it's fully loaded, so try and create the next one. @@ -905,15 +924,15 @@ import java.io.IOException; // The period is not available yet. mediaSource.maybeThrowSourceInfoRefreshError(); } else { - Window window = timeline.getPeriodWindow(newLoadingPeriodIndex); long startPositionUs = loadingPeriod == null ? playbackInfo.positionUs - : newLoadingPeriodIndex == window.startPeriodIndex ? C.UNSET_TIME_US - : 0; + : (newLoadingPeriodIndex == timeline.getWindowFirstPeriodIndex(newLoadingPeriodIndex) + ? C.UNSET_TIME_US : 0); if (startPositionUs == C.UNSET_TIME_US) { // This is the first period of a new window or we don't have a start position, so seek to // the default position for the window. - newLoadingPeriodIndex = window.defaultInitialPeriodIndex; - startPositionUs = window.defaultInitialTimeMs * 1000; + Pair defaultPosition = getDefaultPosition(newLoadingPeriodIndex); + newLoadingPeriodIndex = defaultPosition.first; + startPositionUs = defaultPosition.second; } MediaPeriod mediaPeriod = mediaSource.createPeriod(newLoadingPeriodIndex, this, loadControl.getAllocator(), startPositionUs); 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 f3e4c2fccd..a5d899341b 100644 --- a/library/src/main/java/com/google/android/exoplayer2/Timeline.java +++ b/library/src/main/java/com/google/android/exoplayer2/Timeline.java @@ -101,4 +101,29 @@ public interface Timeline { */ Window getWindow(int windowIndex); + /** + * Returns the index of the first period belonging to the {@link Window} at the specified index. + * + * @param windowIndex The window index. + * @return The index of the first period in the window. + */ + int getWindowFirstPeriodIndex(int windowIndex); + + /** + * Returns the index of the last period belonging to the {@link Window} at the specified index. + * + * @param windowIndex The window index. + * @return The index of the last period in the window. + */ + int getWindowLastPeriodIndex(int windowIndex); + + /** + * Returns the start position of the specified window in the first period belonging to it, in + * microseconds. + * + * @param windowIndex The window index. + * @return The start position of the window in the first period belonging to it. + */ + long getWindowOffsetInFirstPeriodUs(int windowIndex); + } diff --git a/library/src/main/java/com/google/android/exoplayer2/Window.java b/library/src/main/java/com/google/android/exoplayer2/Window.java index bc4da56da9..4db01ab46f 100644 --- a/library/src/main/java/com/google/android/exoplayer2/Window.java +++ b/library/src/main/java/com/google/android/exoplayer2/Window.java @@ -21,8 +21,8 @@ package com.google.android.exoplayer2; public final class Window { /** - * Creates a new {@link Window} consisting of a single period starting at time zero and with the - * specified duration. The default initial position is the start of the window. + * Creates a new {@link Window} consisting of a single period with the specified duration. The + * default start position is zero. * * @param durationUs The duration of the window, in microseconds. * @param isSeekable Whether seeking is supported within the window. @@ -30,77 +30,18 @@ public final class Window { */ public static Window createWindowFromZero(long durationUs, boolean isSeekable, boolean isDynamic) { - return createWindow(0, 0, 0, durationUs, durationUs, isSeekable, isDynamic, 0, 0); + return new Window(durationUs, isSeekable, isDynamic, 0); } /** - * Creates a new {@link Window} representing the specified time range. The default initial - * position is the start of the window. - * - * @param startPeriodIndex The index of the period containing the start of the window. - * @param startTimeUs The start time of the window in microseconds, relative to the start of the - * specified start period. - * @param endPeriodIndex The index of the period containing the end of the window. - * @param endTimeUs The end time of the window in microseconds, relative to the start of the - * specified end period. - * @param durationUs The duration of the window in microseconds. - * @param isSeekable Whether seeking is supported within the window. - * @param isDynamic Whether this seek window may change when the timeline is updated. + * The default position relative to the start of the window at which to start playback, in + * microseconds. */ - public static Window createWindow(int startPeriodIndex, long startTimeUs, - int endPeriodIndex, long endTimeUs, long durationUs, boolean isSeekable, boolean isDynamic) { - return createWindow(startPeriodIndex, startTimeUs, endPeriodIndex, endTimeUs, durationUs, - isSeekable, isDynamic, startPeriodIndex, startTimeUs); - } - + public final long defaultStartPositionUs; /** - * Creates a new {@link Window} representing the specified time range. - * - * @param startPeriodIndex The index of the period containing the start of the window. - * @param startTimeUs The start time of the window in microseconds, relative to the start of the - * specified start period. - * @param endPeriodIndex The index of the period containing the end of the window. - * @param endTimeUs The end time of the window in microseconds, relative to the start of the - * specified end period. - * @param durationUs The duration of the window in microseconds. - * @param isSeekable Whether seeking is supported within the window. - * @param isDynamic Whether this seek window may change when the timeline is updated. - * @param defaultInitialPeriodIndex The index of the period containing the default position from - * which playback should start. - * @param defaultInitialTimeUs The time of the default position from which playback should start - * in microseconds, relative to the start of the period that contains it. + * The duration of the window in microseconds, or {@link C#UNSET_TIME_US} if unknown. */ - public static Window createWindow(int startPeriodIndex, long startTimeUs, - int endPeriodIndex, long endTimeUs, long durationUs, boolean isSeekable, boolean isDynamic, - int defaultInitialPeriodIndex, long defaultInitialTimeUs) { - return new Window(startPeriodIndex, startTimeUs / 1000, endPeriodIndex, - endTimeUs == C.UNSET_TIME_US ? ExoPlayer.UNKNOWN_TIME : (endTimeUs / 1000), - durationUs == C.UNSET_TIME_US ? ExoPlayer.UNKNOWN_TIME : (durationUs / 1000), - isSeekable, isDynamic, defaultInitialPeriodIndex, defaultInitialTimeUs / 1000); - } - - /** - * The period index at the start of the window. - */ - public final int startPeriodIndex; - /** - * The time at the start of the window relative to the start of the period at - * {@link #startPeriodIndex}, in milliseconds. - */ - public final long startTimeMs; - /** - * The period index at the end of the window. - */ - public final int endPeriodIndex; - /** - * The time at the end of the window relative to the start of the period at - * {@link #endPeriodIndex}, in milliseconds. - */ - public final long endTimeMs; - /** - * The duration of the window in milliseconds, or {@link C#UNSET_TIME_US} if unknown. - */ - public final long durationMs; + public final long durationUs; /** * Whether it's possible to seek within the window. */ @@ -109,54 +50,30 @@ public final class Window { * Whether this seek window may change when the timeline is updated. */ public final boolean isDynamic; - /** - * The period index of the default position from which playback should start. - */ - public final int defaultInitialPeriodIndex; - /** - * The time of the default position relative to the start of the period at - * {@link #defaultInitialPeriodIndex}, in milliseconds. - */ - public final long defaultInitialTimeMs; - private Window(int startPeriodIndex, long startTimeMs, int endPeriodIndex, long endTimeMs, - long durationMs, boolean isSeekable, boolean isDynamic, int defaultInitialPeriodIndex, - long defaultInitialTimeMs) { - this.startPeriodIndex = startPeriodIndex; - this.startTimeMs = startTimeMs; - this.endPeriodIndex = endPeriodIndex; - this.endTimeMs = endTimeMs; - this.durationMs = durationMs; + /** + * @param durationUs The duration of the window in microseconds, or {@link C#UNSET_TIME_US} if + * unknown. + * @param isSeekable Whether seeking is supported within the window. + * @param isDynamic Whether this seek window may change when the timeline is updated. + * @param defaultStartPositionUs The default position relative to the start of the window at which + * to start playback, in microseconds. + */ + public Window(long durationUs, boolean isSeekable, boolean isDynamic, + long defaultStartPositionUs) { + this.durationUs = durationUs; this.isSeekable = isSeekable; this.isDynamic = isDynamic; - this.defaultInitialPeriodIndex = defaultInitialPeriodIndex; - this.defaultInitialTimeMs = defaultInitialTimeMs; - } - - /** - * Returns a new window that is offset by the specified number of periods. - * - * @param periodCount The number of periods to add to {@link #startPeriodIndex} and - * {@link #endPeriodIndex} when constructing the copy. - * @return A new window that is offset by the specified number of periods. - */ - public Window copyOffsetByPeriodCount(int periodCount) { - return new Window(startPeriodIndex + periodCount, startTimeMs, endPeriodIndex + periodCount, - endTimeMs, durationMs, isSeekable, isDynamic, defaultInitialPeriodIndex + periodCount, - defaultInitialTimeMs); + this.defaultStartPositionUs = defaultStartPositionUs; } @Override public int hashCode() { int result = 17; - result = 31 * result + startPeriodIndex; - result = 31 * result + (int) startTimeMs; - result = 31 * result + endPeriodIndex; - result = 31 * result + (int) endTimeMs; result = 31 * result + (isSeekable ? 1 : 2); result = 31 * result + (isDynamic ? 1 : 2); - result = 31 * result + defaultInitialPeriodIndex; - result = 31 * result + (int) defaultInitialTimeMs; + result = 31 * result + (int) defaultStartPositionUs; + result = 31 * result + (int) durationUs; return result; } @@ -169,21 +86,16 @@ public final class Window { return false; } Window other = (Window) obj; - return other.startPeriodIndex == startPeriodIndex - && other.startTimeMs == startTimeMs - && other.endPeriodIndex == endPeriodIndex - && other.endTimeMs == endTimeMs - && other.durationMs == durationMs + return other.durationUs == durationUs && other.isSeekable == isSeekable && other.isDynamic == isDynamic - && other.defaultInitialPeriodIndex == defaultInitialPeriodIndex - && other.defaultInitialTimeMs == defaultInitialTimeMs; + && other.defaultStartPositionUs == defaultStartPositionUs; } @Override public String toString() { - return "Window[" + startPeriodIndex + ", " + startTimeMs + ", " + endPeriodIndex + ", " - + endTimeMs + ", " + isDynamic + "]"; + return "Window[" + durationUs + ", " + defaultStartPositionUs + ", " + isSeekable + ", " + + isDynamic + "]"; } } 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 3c4cb95c61..c99f24f3ee 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 @@ -22,7 +22,6 @@ import com.google.android.exoplayer2.source.MediaPeriod.Callback; import com.google.android.exoplayer2.upstream.Allocator; import com.google.android.exoplayer2.util.Util; import java.io.IOException; -import java.util.ArrayList; import java.util.HashMap; import java.util.Map; @@ -58,8 +57,8 @@ public final class ConcatenatingMediaSource implements MediaSource { public void onSourceInfoRefreshed(Timeline sourceTimeline, Object manifest) { timelines[index] = sourceTimeline; manifests[index] = manifest; - for (int i = 0; i < timelines.length; i++) { - if (timelines[i] == null) { + for (Timeline timeline : timelines) { + if (timeline == null) { // Don't invoke the listener until all sources have timelines. return; } @@ -72,16 +71,6 @@ public final class ConcatenatingMediaSource implements MediaSource { } } - @Override - public int getNewPlayingPeriodIndex(int oldPlayingPeriodIndex, Timeline oldConcatenatedTimeline) { - ConcatenatedTimeline oldTimeline = (ConcatenatedTimeline) oldConcatenatedTimeline; - int sourceIndex = oldTimeline.getSourceIndexForPeriod(oldPlayingPeriodIndex); - int oldFirstPeriodIndex = oldTimeline.getFirstPeriodIndexInSource(sourceIndex); - int firstPeriodIndex = timeline.getFirstPeriodIndexInSource(sourceIndex); - return firstPeriodIndex + mediaSources[sourceIndex].getNewPlayingPeriodIndex( - oldPlayingPeriodIndex - oldFirstPeriodIndex, oldTimeline.timelines[sourceIndex]); - } - @Override public void maybeThrowSourceInfoRefreshError() throws IOException { for (MediaSource mediaSource : mediaSources) { @@ -122,22 +111,14 @@ public final class ConcatenatingMediaSource implements MediaSource { private final Timeline[] timelines; private final int[] sourcePeriodOffsets; private final int[] sourceWindowOffsets; - private final Window[] windows; public ConcatenatedTimeline(Timeline[] timelines) { 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]; - // Offset the windows so they are relative to the source. - int sourceWindowCount = timeline.getWindowCount(); - for (int j = 0; j < sourceWindowCount; j++) { - Window sourceWindow = timeline.getWindow(j); - concatenatedWindows.add(sourceWindow.copyOffsetByPeriodCount(periodCount)); - } periodCount += timeline.getPeriodCount(); sourcePeriodOffsets[i] = periodCount; windowCount += timeline.getWindowCount(); @@ -146,7 +127,6 @@ public final class ConcatenatingMediaSource implements MediaSource { this.timelines = timelines; this.sourcePeriodOffsets = sourcePeriodOffsets; this.sourceWindowOffsets = sourceWindowOffsets; - windows = concatenatedWindows.toArray(new Window[concatenatedWindows.size()]); } @Override @@ -183,7 +163,7 @@ public final class ConcatenatingMediaSource implements MediaSource { @Override public Window getPeriodWindow(int periodIndex) { - return windows[getPeriodWindowIndex(periodIndex)]; + return getWindow(getPeriodWindowIndex(periodIndex)); } @Override @@ -199,12 +179,11 @@ public final class ConcatenatingMediaSource implements MediaSource { if (!(id instanceof Pair)) { return NO_PERIOD_INDEX; } - @SuppressWarnings("unchecked") - Pair sourceIndexAndPeriodId = (Pair) id; + Pair sourceIndexAndPeriodId = (Pair) id; if (!(sourceIndexAndPeriodId.first instanceof Integer)) { return NO_PERIOD_INDEX; } - int sourceIndex = sourceIndexAndPeriodId.first; + int sourceIndex = (int) sourceIndexAndPeriodId.first; Object periodId = sourceIndexAndPeriodId.second; if (sourceIndex < 0 || sourceIndex >= timelines.length) { return NO_PERIOD_INDEX; @@ -216,12 +195,39 @@ public final class ConcatenatingMediaSource implements MediaSource { @Override public int getWindowCount() { - return windows.length; + return sourceWindowOffsets[sourceWindowOffsets.length - 1]; } @Override public Window getWindow(int windowIndex) { - return windows[windowIndex]; + int sourceIndex = getSourceIndexForWindow(windowIndex); + int firstWindowIndexInSource = getFirstWindowIndexInSource(sourceIndex); + return timelines[sourceIndex].getWindow(windowIndex - firstWindowIndexInSource); + } + + @Override + public int getWindowFirstPeriodIndex(int windowIndex) { + int sourceIndex = getSourceIndexForWindow(windowIndex); + int firstPeriodIndexInSource = getFirstPeriodIndexInSource(sourceIndex); + int firstWindowIndexInSource = getFirstWindowIndexInSource(sourceIndex); + return firstPeriodIndexInSource + timelines[sourceIndex].getWindowFirstPeriodIndex( + windowIndex - firstWindowIndexInSource); + } + + @Override + public int getWindowLastPeriodIndex(int windowIndex) {int sourceIndex = getSourceIndexForWindow(windowIndex); + int firstPeriodIndexInSource = getFirstPeriodIndexInSource(sourceIndex); + int firstWindowIndexInSource = getFirstWindowIndexInSource(sourceIndex); + return firstPeriodIndexInSource + timelines[sourceIndex].getWindowLastPeriodIndex( + windowIndex - firstWindowIndexInSource); + } + + @Override + public long getWindowOffsetInFirstPeriodUs(int windowIndex) { + int sourceIndex = getSourceIndexForWindow(windowIndex); + int firstWindowIndexInSource = getFirstPeriodIndexInSource(sourceIndex); + return timelines[sourceIndex].getWindowOffsetInFirstPeriodUs( + windowIndex - firstWindowIndexInSource); } private int getSourceIndexForPeriod(int periodIndex) { @@ -232,6 +238,14 @@ public final class ConcatenatingMediaSource implements MediaSource { return sourceIndex == 0 ? 0 : sourcePeriodOffsets[sourceIndex - 1]; } + private int getSourceIndexForWindow(int windowIndex) { + return Util.binarySearchFloor(sourceWindowOffsets, windowIndex, true, false) + 1; + } + + private int getFirstWindowIndexInSource(int sourceIndex) { + return sourceIndex == 0 ? 0 : sourceWindowOffsets[sourceIndex - 1]; + } + } } diff --git a/library/src/main/java/com/google/android/exoplayer2/source/ExtractorMediaSource.java b/library/src/main/java/com/google/android/exoplayer2/source/ExtractorMediaSource.java index 5f5df71333..0ed492ec50 100644 --- a/library/src/main/java/com/google/android/exoplayer2/source/ExtractorMediaSource.java +++ b/library/src/main/java/com/google/android/exoplayer2/source/ExtractorMediaSource.java @@ -139,11 +139,6 @@ public final class ExtractorMediaSource implements MediaSource, MediaSource.List listener.onSourceInfoRefreshed(timeline, null); } - @Override - public int getNewPlayingPeriodIndex(int oldPlayingPeriodIndex, Timeline oldTimeline) { - return 0; - } - @Override public void maybeThrowSourceInfoRefreshError() throws IOException { // Do nothing. diff --git a/library/src/main/java/com/google/android/exoplayer2/source/MediaSource.java b/library/src/main/java/com/google/android/exoplayer2/source/MediaSource.java index f15e9b782f..4c0686eec1 100644 --- a/library/src/main/java/com/google/android/exoplayer2/source/MediaSource.java +++ b/library/src/main/java/com/google/android/exoplayer2/source/MediaSource.java @@ -47,19 +47,6 @@ public interface MediaSource { */ void prepareSource(Listener listener); - /** - * Returns the period index to play in this source's new timeline, or - * {@link Timeline#NO_PERIOD_INDEX} if the player should stop playback. The - * {@code oldPlayingPeriodIndex} should be an index of a period in the old timeline that is no - * longer present (based on its identifier) in the new timeline. - * - * @param oldPlayingPeriodIndex The period index that was being played in the old timeline. - * @param oldTimeline The old timeline. - * @return The new period index to play in this source's new timeline. Playback will resume from - * the default start position in the new period index. - */ - int getNewPlayingPeriodIndex(int oldPlayingPeriodIndex, Timeline oldTimeline); - /** * Throws any pending error encountered while loading or refreshing source information. */ diff --git a/library/src/main/java/com/google/android/exoplayer2/source/MergingMediaSource.java b/library/src/main/java/com/google/android/exoplayer2/source/MergingMediaSource.java index 1d06dab5f0..b0b9608622 100644 --- a/library/src/main/java/com/google/android/exoplayer2/source/MergingMediaSource.java +++ b/library/src/main/java/com/google/android/exoplayer2/source/MergingMediaSource.java @@ -65,11 +65,6 @@ public final class MergingMediaSource implements MediaSource { } } - @Override - public int getNewPlayingPeriodIndex(int oldPlayingPeriodIndex, Timeline oldTimeline) { - return mediaSources[0].getNewPlayingPeriodIndex(oldPlayingPeriodIndex, oldTimeline); - } - @Override public void maybeThrowSourceInfoRefreshError() throws IOException { for (MediaSource mediaSource : mediaSources) { 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 d734849447..1887ab0a58 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 @@ -28,7 +28,7 @@ public final class SinglePeriodTimeline implements Timeline { private static final Object ID = new Object(); - private final long durationUs; + private final long offsetInFirstPeriodUs; private final Window window; /** @@ -39,18 +39,18 @@ public final class SinglePeriodTimeline implements Timeline { * @param isSeekable Whether seeking is supported within the period. */ public SinglePeriodTimeline(long durationUs, boolean isSeekable) { - this(durationUs, Window.createWindowFromZero(durationUs, isSeekable, false /* isDynamic */)); + this(0, Window.createWindowFromZero(durationUs, isSeekable, false /* isDynamic */)); } /** * Creates a timeline with one period of known duration and a window extending from zero to its * duration. * - * @param durationUs The duration of the period, in microseconds. + * @param offsetInFirstPeriodUs The offset of the start of the window in the period. * @param window The available window within the period. */ - public SinglePeriodTimeline(long durationUs, Window window) { - this.durationUs = durationUs; + public SinglePeriodTimeline(long offsetInFirstPeriodUs, Window window) { + this.offsetInFirstPeriodUs = offsetInFirstPeriodUs; this.window = window; } @@ -67,13 +67,15 @@ public final class SinglePeriodTimeline implements Timeline { @Override public long getPeriodDurationMs(int periodIndex) { Assertions.checkIndex(periodIndex, 0, 1); - return durationUs == C.UNSET_TIME_US ? ExoPlayer.UNKNOWN_TIME : (durationUs / 1000); + return window.durationUs == C.UNSET_TIME_US ? ExoPlayer.UNKNOWN_TIME + : ((offsetInFirstPeriodUs + window.durationUs) / 1000); } @Override public long getPeriodDurationUs(int periodIndex) { Assertions.checkIndex(periodIndex, 0, 1); - return durationUs; + return window.durationUs == C.UNSET_TIME_US ? C.UNSET_TIME_US + : (offsetInFirstPeriodUs + window.durationUs); } @Override @@ -110,4 +112,22 @@ public final class SinglePeriodTimeline implements Timeline { return window; } + @Override + public int getWindowFirstPeriodIndex(int windowIndex) { + Assertions.checkIndex(windowIndex, 0, 1); + return 0; + } + + @Override + public int getWindowLastPeriodIndex(int windowIndex) { + Assertions.checkIndex(windowIndex, 0, 1); + return 0; + } + + @Override + public long getWindowOffsetInFirstPeriodUs(int windowIndex) { + Assertions.checkIndex(windowIndex, 0, 1); + return offsetInFirstPeriodUs; + } + } diff --git a/library/src/main/java/com/google/android/exoplayer2/source/SingleSampleMediaSource.java b/library/src/main/java/com/google/android/exoplayer2/source/SingleSampleMediaSource.java index c5e1d72b98..c0902f5a84 100644 --- a/library/src/main/java/com/google/android/exoplayer2/source/SingleSampleMediaSource.java +++ b/library/src/main/java/com/google/android/exoplayer2/source/SingleSampleMediaSource.java @@ -89,11 +89,6 @@ public final class SingleSampleMediaSource implements MediaSource { listener.onSourceInfoRefreshed(timeline, null); } - @Override - public int getNewPlayingPeriodIndex(int oldPlayingPeriodIndex, Timeline oldTimeline) { - return oldPlayingPeriodIndex; - } - @Override public void maybeThrowSourceInfoRefreshError() throws IOException { // Do nothing. 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 edf64051e0..5427de7dea 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 @@ -161,12 +161,6 @@ public final class DashMediaSource implements MediaSource { startLoadingManifest(); } - @Override - public int getNewPlayingPeriodIndex(int oldPlayingPeriodIndex, Timeline oldTimeline) { - // Seek to the default position, which is the live edge for live sources. - return 0; - } - @Override public void maybeThrowSourceInfoRefreshError() throws IOException { loader.maybeThrowError(); @@ -381,29 +375,19 @@ public final class DashMediaSource implements MediaSource { for (int i = 0; i < manifest.getPeriodCount() - 1; i++) { windowDurationUs += manifest.getPeriodDurationUs(i); } - int defaultInitialPeriodIndex = 0; long defaultInitialTimeUs = 0; if (manifest.dynamic) { - defaultInitialPeriodIndex = lastPeriodIndex; long liveEdgeOffsetForManifestMs = liveEdgeOffsetMs; if (liveEdgeOffsetForManifestMs == DEFAULT_LIVE_EDGE_OFFSET_PREFER_MANIFEST_MS) { liveEdgeOffsetForManifestMs = manifest.suggestedPresentationDelay != -1 ? manifest.suggestedPresentationDelay : DEFAULT_LIVE_EDGE_OFFSET_FIXED_MS; } - defaultInitialTimeUs = currentEndTimeUs - (liveEdgeOffsetForManifestMs * 1000); - while (defaultInitialTimeUs < 0 && defaultInitialPeriodIndex > 0) { - defaultInitialPeriodIndex--; - defaultInitialTimeUs += manifest.getPeriodDurationUs(defaultInitialPeriodIndex); - } - if (defaultInitialPeriodIndex == 0) { - defaultInitialTimeUs = Math.max(defaultInitialTimeUs, currentStartTimeUs); - } + defaultInitialTimeUs = Math.max(0, windowDurationUs - (liveEdgeOffsetForManifestMs * 1000)); } - window = Window.createWindow(0, currentStartTimeUs, lastPeriodIndex, currentEndTimeUs, - windowDurationUs, true /* isSeekable */, manifest.dynamic, defaultInitialPeriodIndex, + window = new Window(windowDurationUs, true /* isSeekable */, manifest.dynamic, defaultInitialTimeUs); - sourceListener.onSourceInfoRefreshed(new DashTimeline(firstPeriodId, manifest, window), - manifest); + sourceListener.onSourceInfoRefreshed(new DashTimeline(firstPeriodId, currentStartTimeUs, + manifest, window), manifest); } private void scheduleManifestRefresh() { @@ -485,11 +469,14 @@ public final class DashMediaSource implements MediaSource { private static final class DashTimeline implements Timeline { private final int firstPeriodId; + private final long offsetInFirstPeriodUs; private final DashManifest manifest; private final Window window; - public DashTimeline(int firstPeriodId, DashManifest manifest, Window window) { + public DashTimeline(int firstPeriodId, long offsetInFirstPeriodUs, DashManifest manifest, + Window window) { this.firstPeriodId = firstPeriodId; + this.offsetInFirstPeriodUs = offsetInFirstPeriodUs; this.manifest = manifest; this.window = window; } @@ -554,6 +541,21 @@ public final class DashMediaSource implements MediaSource { return window; } + @Override + public int getWindowFirstPeriodIndex(int windowIndex) { + return 0; + } + + @Override + public int getWindowLastPeriodIndex(int windowIndex) { + return manifest.getPeriodCount() - 1; + } + + @Override + public long getWindowOffsetInFirstPeriodUs(int windowIndex) { + return offsetInFirstPeriodUs; + } + } private final class ManifestCallback implements diff --git a/library/src/main/java/com/google/android/exoplayer2/source/hls/HlsMediaSource.java b/library/src/main/java/com/google/android/exoplayer2/source/hls/HlsMediaSource.java index fcc8093ef1..d6c51d415a 100644 --- a/library/src/main/java/com/google/android/exoplayer2/source/hls/HlsMediaSource.java +++ b/library/src/main/java/com/google/android/exoplayer2/source/hls/HlsMediaSource.java @@ -18,7 +18,6 @@ package com.google.android.exoplayer2.source.hls; import android.net.Uri; import android.os.Handler; import com.google.android.exoplayer2.C; -import com.google.android.exoplayer2.Timeline; import com.google.android.exoplayer2.source.AdaptiveMediaSourceEventListener; import com.google.android.exoplayer2.source.AdaptiveMediaSourceEventListener.EventDispatcher; import com.google.android.exoplayer2.source.MediaPeriod; @@ -68,11 +67,6 @@ public final class HlsMediaSource implements MediaSource { listener.onSourceInfoRefreshed(new SinglePeriodTimeline(C.UNSET_TIME_US, false), null); } - @Override - public int getNewPlayingPeriodIndex(int oldPlayingPeriodIndex, Timeline oldTimeline) { - return oldPlayingPeriodIndex; - } - @Override public void maybeThrowSourceInfoRefreshError() { // Do nothing. diff --git a/library/src/main/java/com/google/android/exoplayer2/source/smoothstreaming/SsMediaSource.java b/library/src/main/java/com/google/android/exoplayer2/source/smoothstreaming/SsMediaSource.java index 5730e05980..4691aa40ec 100644 --- a/library/src/main/java/com/google/android/exoplayer2/source/smoothstreaming/SsMediaSource.java +++ b/library/src/main/java/com/google/android/exoplayer2/source/smoothstreaming/SsMediaSource.java @@ -108,11 +108,6 @@ public final class SsMediaSource implements MediaSource, startLoadingManifest(); } - @Override - public int getNewPlayingPeriodIndex(int oldPlayingPeriodIndex, Timeline oldTimeline) { - return oldPlayingPeriodIndex; - } - @Override public void maybeThrowSourceInfoRefreshError() throws IOException { manifestLoader.maybeThrowError(); @@ -182,11 +177,10 @@ public final class SsMediaSource implements MediaSource, startTimeUs = Math.max(startTimeUs, endTimeUs - manifest.dvrWindowLengthUs); } long durationUs = endTimeUs - startTimeUs; - long defaultInitialStartPositionUs = Math.max(startTimeUs, - endTimeUs - (liveEdgeOffsetMs * 1000)); - Window window = Window.createWindow(0, startTimeUs, 0, endTimeUs, durationUs, - true /* isSeekable */, true /* isDynamic */, 0, defaultInitialStartPositionUs); - timeline = new SinglePeriodTimeline(endTimeUs, window); + long defaultInitialStartPositionUs = Math.max(0, durationUs - (liveEdgeOffsetMs * 1000)); + Window window = new Window(durationUs, true /* isSeekable */, true /* isDynamic */, + defaultInitialStartPositionUs); + timeline = new SinglePeriodTimeline(startTimeUs, window); } } else { boolean isSeekable = manifest.durationUs != C.UNSET_TIME_US;