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 37b12c3a03..42c4109ddb 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 @@ -19,18 +19,29 @@ import com.google.android.exoplayer2.C; import com.google.android.exoplayer2.util.Assertions; /** - * A {@link Timeline} consisting of a single period. + * A {@link Timeline} consisting of a single period and seek window. */ public final class SinglePeriodTimeline implements Timeline { /** - * Returns a new timeline with one period of unknown duration and no seek window. + * Returns a new timeline with one period of unknown duration and an empty seek window. * * @param id The identifier for the period. * @return A new timeline with one period of unknown duration. */ public static Timeline createNonFinalTimeline(Object id) { - return new SinglePeriodTimeline(id, false, C.UNSET_TIME_US); + return new SinglePeriodTimeline(id, false, C.UNSET_TIME_US, SeekWindow.UNSEEKABLE); + } + + /** + * Returns a new timeline with one period of unknown duration and the specified seek window. + * + * @param id The identifier for the period. + * @param seekWindow The seek window. + * @return A new timeline with one period of unknown duration. + */ + public static Timeline createNonFinalTimeline(Object id, SeekWindow seekWindow) { + return new SinglePeriodTimeline(id, false, C.UNSET_TIME_US, seekWindow); } /** @@ -59,14 +70,13 @@ public final class SinglePeriodTimeline implements Timeline { private final Object id; private final boolean isFinal; private final long durationMs; - private final SeekWindow[] seekWindows; + private final SeekWindow seekWindow; - private SinglePeriodTimeline(Object id, boolean isFinal, long durationMs, - SeekWindow... seekWindows) { + private SinglePeriodTimeline(Object id, boolean isFinal, long durationMs, SeekWindow seekWindow) { this.id = Assertions.checkNotNull(id); this.isFinal = isFinal; this.durationMs = durationMs; - this.seekWindows = seekWindows; + this.seekWindow = seekWindow; } @Override @@ -99,12 +109,12 @@ public final class SinglePeriodTimeline implements Timeline { @Override public int getSeekWindowCount() { - return seekWindows.length; + return 1; } @Override public SeekWindow getSeekWindow(int index) { - return seekWindows[index]; + return seekWindow; } } 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 a04a007a08..614cc1b6c2 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 @@ -24,9 +24,11 @@ import com.google.android.exoplayer2.source.AdaptiveMediaSourceEventListener; import com.google.android.exoplayer2.source.AdaptiveMediaSourceEventListener.EventDispatcher; import com.google.android.exoplayer2.source.MediaPeriod; import com.google.android.exoplayer2.source.MediaSource; +import com.google.android.exoplayer2.source.SeekWindow; import com.google.android.exoplayer2.source.SinglePeriodTimeline; import com.google.android.exoplayer2.source.Timeline; import com.google.android.exoplayer2.source.smoothstreaming.manifest.SsManifest; +import com.google.android.exoplayer2.source.smoothstreaming.manifest.SsManifest.StreamElement; import com.google.android.exoplayer2.source.smoothstreaming.manifest.SsManifestParser; import com.google.android.exoplayer2.upstream.DataSource; import com.google.android.exoplayer2.upstream.Loader; @@ -45,6 +47,11 @@ public final class SsMediaSource implements MediaSource, * The default minimum number of times to retry loading data prior to failing. */ public static final int DEFAULT_MIN_LOADABLE_RETRY_COUNT = 3; + /** + * The offset in microseconds subtracted from the live edge position when calculating the default + * position returned by {@link #getDefaultStartPosition(int)}. + */ + private static final long LIVE_EDGE_OFFSET_US = 30000000; private static final int MINIMUM_MANIFEST_REFRESH_PERIOD_MS = 5000; @@ -61,6 +68,7 @@ public final class SsMediaSource implements MediaSource, private long manifestLoadStartTimestamp; private SsManifest manifest; + private SeekWindow seekWindow; private Handler manifestRefreshHandler; private SsMediaPeriod period; @@ -102,7 +110,14 @@ public final class SsMediaSource implements MediaSource, @Override public Position getDefaultStartPosition(int index) { - // TODO: Return the position of the live edge, if applicable. + if (seekWindow == null) { + return null; + } + if (manifest.isLive) { + long startPositionUs = Math.max(seekWindow.startTimeUs, + seekWindow.endTimeUs - LIVE_EDGE_OFFSET_US); + return new Position(0, startPositionUs); + } return Position.DEFAULT; } @@ -144,9 +159,27 @@ public final class SsMediaSource implements MediaSource, } else { period.updateManifest(manifest); } - Timeline timeline = manifest.isLive || manifest.durationUs == C.UNSET_TIME_US - ? SinglePeriodTimeline.createUnseekableFinalTimeline(this, C.UNSET_TIME_US) - : SinglePeriodTimeline.createSeekableFinalTimeline(this, manifest.durationUs); + Timeline timeline; + if (manifest.isLive) { + long startTimeUs = Long.MAX_VALUE; + for (int i = 0; i < manifest.streamElements.length; i++) { + StreamElement element = manifest.streamElements[i]; + if (element.chunkCount > 0) { + startTimeUs = Math.min(startTimeUs, element.getStartTimeUs(0)); + } + } + if (startTimeUs == Long.MAX_VALUE) { + timeline = SinglePeriodTimeline.createNonFinalTimeline(this); + } else { + timeline = SinglePeriodTimeline.createNonFinalTimeline(this, + new SeekWindow(0, startTimeUs, 0, startTimeUs + manifest.dvrWindowLengthUs)); + } + } else if (manifest.durationUs == C.UNSET_TIME_US) { + timeline = SinglePeriodTimeline.createUnseekableFinalTimeline(this, C.UNSET_TIME_US); + } else { + timeline = SinglePeriodTimeline.createSeekableFinalTimeline(this, manifest.durationUs); + } + seekWindow = timeline.getSeekWindow(0); sourceListener.onSourceInfoRefreshed(timeline, manifest); scheduleManifestRefresh(); }