diff --git a/library/src/main/java/com/google/android/exoplayer/dash/DashChunkSource.java b/library/src/main/java/com/google/android/exoplayer/dash/DashChunkSource.java index 932a8ea598..8f0dc8b71c 100644 --- a/library/src/main/java/com/google/android/exoplayer/dash/DashChunkSource.java +++ b/library/src/main/java/com/google/android/exoplayer/dash/DashChunkSource.java @@ -326,10 +326,13 @@ public class DashChunkSource implements ChunkSource { return; } + int lastSegmentNum = segmentIndex.getLastSegmentNum(); + boolean indexUnbounded = lastSegmentNum == DashSegmentIndex.INDEX_UNBOUNDED; + int segmentNum; if (queue.isEmpty()) { if (currentManifest.dynamic) { - seekPositionUs = getLiveSeekPosition(); + seekPositionUs = getLiveSeekPosition(indexUnbounded); } segmentNum = segmentIndex.getSegmentNum(seekPositionUs); } else { @@ -337,16 +340,18 @@ public class DashChunkSource implements ChunkSource { - representationHolder.segmentNumShift; } + // TODO: For unbounded manifests, we need to enforce that we don't try and request chunks + // behind or in front of the live window. if (currentManifest.dynamic) { if (segmentNum < segmentIndex.getFirstSegmentNum()) { // This is before the first chunk in the current manifest. fatalError = new BehindLiveWindowException(); return; - } else if (segmentNum > segmentIndex.getLastSegmentNum()) { + } else if (!indexUnbounded && segmentNum > lastSegmentNum) { // This is beyond the last chunk in the current manifest. finishedCurrentManifest = true; return; - } else if (segmentNum == segmentIndex.getLastSegmentNum()) { + } else if (!indexUnbounded && segmentNum == lastSegmentNum) { // This is the last chunk in the current manifest. Mark the manifest as being finished, // but continue to return the final chunk. finishedCurrentManifest = true; @@ -452,16 +457,24 @@ public class DashChunkSource implements ChunkSource { * For live playbacks, determines the seek position that snaps playback to be * {@link #liveEdgeLatencyUs} behind the live edge of the current manifest * + * @param indexUnbounded True if the segment index for this source is unbounded. False otherwise. * @return The seek position in microseconds. */ - private long getLiveSeekPosition() { - long liveEdgeTimestampUs = Long.MIN_VALUE; - for (RepresentationHolder representationHolder : representationHolders.values()) { - DashSegmentIndex segmentIndex = representationHolder.segmentIndex; - int lastSegmentNum = segmentIndex.getLastSegmentNum(); - long indexLiveEdgeTimestampUs = segmentIndex.getTimeUs(lastSegmentNum) - + segmentIndex.getDurationUs(lastSegmentNum); - liveEdgeTimestampUs = Math.max(liveEdgeTimestampUs, indexLiveEdgeTimestampUs); + private long getLiveSeekPosition(boolean indexUnbounded) { + long liveEdgeTimestampUs; + if (indexUnbounded) { + // TODO: Use UtcTimingElement where possible. + long nowMs = System.currentTimeMillis(); + liveEdgeTimestampUs = (nowMs - currentManifest.availabilityStartTime) * 1000; + } else { + liveEdgeTimestampUs = Long.MIN_VALUE; + for (RepresentationHolder representationHolder : representationHolders.values()) { + DashSegmentIndex segmentIndex = representationHolder.segmentIndex; + int lastSegmentNum = segmentIndex.getLastSegmentNum(); + long indexLiveEdgeTimestampUs = segmentIndex.getTimeUs(lastSegmentNum) + + segmentIndex.getDurationUs(lastSegmentNum); + liveEdgeTimestampUs = Math.max(liveEdgeTimestampUs, indexLiveEdgeTimestampUs); + } } return liveEdgeTimestampUs - liveEdgeLatencyUs; } diff --git a/library/src/main/java/com/google/android/exoplayer/dash/DashSegmentIndex.java b/library/src/main/java/com/google/android/exoplayer/dash/DashSegmentIndex.java index 336e4c6057..e66aa38380 100644 --- a/library/src/main/java/com/google/android/exoplayer/dash/DashSegmentIndex.java +++ b/library/src/main/java/com/google/android/exoplayer/dash/DashSegmentIndex.java @@ -24,6 +24,8 @@ import com.google.android.exoplayer.dash.mpd.RangedUri; */ public interface DashSegmentIndex { + public static final int INDEX_UNBOUNDED = -1; + /** * Returns the segment number of the segment containing a given media time. * @@ -64,9 +66,15 @@ public interface DashSegmentIndex { int getFirstSegmentNum(); /** - * Returns the segment number of the last segment. + * Returns the segment number of the last segment, or {@link #INDEX_UNBOUNDED}. + *
+ * An unbounded index occurs if a live stream manifest uses SegmentTemplate elements without a
+ * SegmentTimeline element. In this case the manifest can be used to derive information about
+ * segments arbitrarily far into the future. This means that the manifest does not need to be
+ * refreshed as frequently (if at all) during playback, however it is necessary for a player to
+ * manually calculate the window of currently available segments.
*
- * @return The segment number of the last segment.
+ * @return The segment number of the last segment, or {@link #INDEX_UNBOUNDED}.
*/
int getLastSegmentNum();
diff --git a/library/src/main/java/com/google/android/exoplayer/dash/mpd/MediaPresentationDescriptionParser.java b/library/src/main/java/com/google/android/exoplayer/dash/mpd/MediaPresentationDescriptionParser.java
index a8ed7c03f2..4aa624cca1 100644
--- a/library/src/main/java/com/google/android/exoplayer/dash/mpd/MediaPresentationDescriptionParser.java
+++ b/library/src/main/java/com/google/android/exoplayer/dash/mpd/MediaPresentationDescriptionParser.java
@@ -356,7 +356,7 @@ public class MediaPresentationDescriptionParser extends DefaultHandler
}
protected SegmentList parseSegmentList(XmlPullParser xpp, Uri baseUrl, SegmentList parent,
- long periodDuration) throws XmlPullParserException, IOException {
+ long periodDurationMs) throws XmlPullParserException, IOException {
long timescale = parseLong(xpp, "timescale", parent != null ? parent.timescale : 1);
long presentationTimeOffset = parseLong(xpp, "presentationTimeOffset",
@@ -388,19 +388,19 @@ public class MediaPresentationDescriptionParser extends DefaultHandler
segments = segments != null ? segments : parent.mediaSegments;
}
- return buildSegmentList(initialization, timescale, presentationTimeOffset, periodDuration,
+ return buildSegmentList(initialization, timescale, presentationTimeOffset, periodDurationMs,
startNumber, duration, timeline, segments);
}
protected SegmentList buildSegmentList(RangedUri initialization, long timescale,
- long presentationTimeOffset, long periodDuration, int startNumber, long duration,
+ long presentationTimeOffset, long periodDurationMs, int startNumber, long duration,
List