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 99845c057e..eec99521f1 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 @@ -572,22 +572,28 @@ public final class DashMediaSource implements MediaSource { long availableStartTimeUs = 0; long availableEndTimeUs = Long.MAX_VALUE; boolean isIndexExplicit = false; + boolean seenEmptyIndex = false; for (int i = 0; i < adaptationSetCount; i++) { DashSegmentIndex index = period.adaptationSets.get(i).representations.get(0).getIndex(); if (index == null) { return new PeriodSeekInfo(true, 0, durationUs); } - int firstSegmentNum = index.getFirstSegmentNum(); - int lastSegmentNum = index.getLastSegmentNum(durationUs); isIndexExplicit |= index.isExplicit(); - long adaptationSetAvailableStartTimeUs = index.getTimeUs(firstSegmentNum); - availableStartTimeUs = Math.max(availableStartTimeUs, adaptationSetAvailableStartTimeUs); - if (lastSegmentNum != DashSegmentIndex.INDEX_UNBOUNDED) { - long adaptationSetAvailableEndTimeUs = index.getTimeUs(lastSegmentNum) - + index.getDurationUs(lastSegmentNum, durationUs); - availableEndTimeUs = Math.min(availableEndTimeUs, adaptationSetAvailableEndTimeUs); - } else { - // The available end time is unmodified, because this index is unbounded. + int segmentCount = index.getSegmentCount(durationUs); + if (segmentCount == 0) { + seenEmptyIndex = true; + availableStartTimeUs = 0; + availableEndTimeUs = 0; + } else if (!seenEmptyIndex) { + int firstSegmentNum = index.getFirstSegmentNum(); + long adaptationSetAvailableStartTimeUs = index.getTimeUs(firstSegmentNum); + availableStartTimeUs = Math.max(availableStartTimeUs, adaptationSetAvailableStartTimeUs); + if (segmentCount != DashSegmentIndex.INDEX_UNBOUNDED) { + int lastSegmentNum = firstSegmentNum + segmentCount - 1; + long adaptationSetAvailableEndTimeUs = index.getTimeUs(lastSegmentNum) + + index.getDurationUs(lastSegmentNum, durationUs); + availableEndTimeUs = Math.min(availableEndTimeUs, adaptationSetAvailableEndTimeUs); + } } } return new PeriodSeekInfo(isIndexExplicit, availableStartTimeUs, availableEndTimeUs); @@ -704,8 +710,8 @@ public final class DashMediaSource implements MediaSource { // not correspond to the start of a segment in both, but this is an edge case. DashSegmentIndex snapIndex = period.adaptationSets.get(videoAdaptationSetIndex) .representations.get(0).getIndex(); - if (snapIndex == null) { - // Video adaptation set does not include an index for snapping. + if (snapIndex == null || snapIndex.getSegmentCount(periodDurationUs) == 0) { + // Video adaptation set does not include a non-empty index for snapping. return windowDefaultStartPositionUs; } int segmentNum = snapIndex.getSegmentNum(defaultStartPositionInPeriodUs, periodDurationUs); diff --git a/library/src/main/java/com/google/android/exoplayer2/source/dash/DashSegmentIndex.java b/library/src/main/java/com/google/android/exoplayer2/source/dash/DashSegmentIndex.java index d002831c4f..2ddc7f4f80 100644 --- a/library/src/main/java/com/google/android/exoplayer2/source/dash/DashSegmentIndex.java +++ b/library/src/main/java/com/google/android/exoplayer2/source/dash/DashSegmentIndex.java @@ -26,12 +26,10 @@ public interface DashSegmentIndex { int INDEX_UNBOUNDED = -1; /** - * Returns the segment number of the segment containing a given media time. - *

- * If the given media time is outside the range of the index, then the returned segment number is - * clamped to {@link #getFirstSegmentNum()} (if the given media time is earlier the start of the - * first segment) or {@link #getLastSegmentNum(long)} (if the given media time is later then the - * end of the last segment). + * Returns {@code getFirstSegmentNum()} if the index has no segments or if the given media time is + * earlier than the start of the first segment. Returns {@code getFirstSegmentNum() + + * getSegmentCount() - 1} if the given media time is later than the end of the last segment. + * Otherwise, returns the segment number of the segment containing the given media time. * * @param timeUs The time in microseconds. * @param periodDurationUs The duration of the enclosing period in microseconds, or @@ -74,7 +72,7 @@ public interface DashSegmentIndex { int getFirstSegmentNum(); /** - * Returns the segment number of the last segment, or {@link #INDEX_UNBOUNDED}. + * Returns the number of segments in the index, or {@link #INDEX_UNBOUNDED}. *

* An unbounded index occurs if a dynamic manifest uses SegmentTemplate elements without a * SegmentTimeline element, and if the period duration is not yet known. In this case the caller @@ -82,9 +80,9 @@ public interface DashSegmentIndex { * * @param periodDurationUs The duration of the enclosing period in microseconds, or * {@link C#TIME_UNSET} if the period's duration is not yet known. - * @return The segment number of the last segment, or {@link #INDEX_UNBOUNDED}. + * @return The number of segments in the index, or {@link #INDEX_UNBOUNDED}. */ - int getLastSegmentNum(long periodDurationUs); + int getSegmentCount(long periodDurationUs); /** * Returns true if segments are defined explicitly by the index. diff --git a/library/src/main/java/com/google/android/exoplayer2/source/dash/DashWrappingSegmentIndex.java b/library/src/main/java/com/google/android/exoplayer2/source/dash/DashWrappingSegmentIndex.java index 56ea626120..40f3448f6a 100644 --- a/library/src/main/java/com/google/android/exoplayer2/source/dash/DashWrappingSegmentIndex.java +++ b/library/src/main/java/com/google/android/exoplayer2/source/dash/DashWrappingSegmentIndex.java @@ -39,8 +39,8 @@ import com.google.android.exoplayer2.source.dash.manifest.RangedUri; } @Override - public int getLastSegmentNum(long periodDurationUs) { - return chunkIndex.length - 1; + public int getSegmentCount(long periodDurationUs) { + return chunkIndex.length; } @Override diff --git a/library/src/main/java/com/google/android/exoplayer2/source/dash/DefaultDashChunkSource.java b/library/src/main/java/com/google/android/exoplayer2/source/dash/DefaultDashChunkSource.java index c553e4eb40..4548bc75f8 100644 --- a/library/src/main/java/com/google/android/exoplayer2/source/dash/DefaultDashChunkSource.java +++ b/library/src/main/java/com/google/android/exoplayer2/source/dash/DefaultDashChunkSource.java @@ -194,10 +194,16 @@ public class DefaultDashChunkSource implements DashChunkSource { } long nowUs = getNowUnixTimeUs(); + int availableSegmentCount = representationHolder.getSegmentCount(); + if (availableSegmentCount == 0) { + // The index doesn't define any segments. + out.endOfStream = !manifest.dynamic || (periodIndex < manifest.getPeriodCount() - 1); + return; + } + int firstAvailableSegmentNum = representationHolder.getFirstSegmentNum(); - int lastAvailableSegmentNum = representationHolder.getLastSegmentNum(); - boolean indexUnbounded = lastAvailableSegmentNum == DashSegmentIndex.INDEX_UNBOUNDED; - if (indexUnbounded) { + int lastAvailableSegmentNum; + if (availableSegmentCount == DashSegmentIndex.INDEX_UNBOUNDED) { // The index is itself unbounded. We need to use the current time to calculate the range of // available segments. long liveEdgeTimeUs = nowUs - manifest.availabilityStartTime * 1000; @@ -211,6 +217,8 @@ public class DefaultDashChunkSource implements DashChunkSource { // getSegmentNum(liveEdgeTimestampUs) will not be completed yet, so subtract one to get the // index of the last completed segment. lastAvailableSegmentNum = representationHolder.getSegmentNum(liveEdgeTimeInPeriodUs) - 1; + } else { + lastAvailableSegmentNum = firstAvailableSegmentNum + availableSegmentCount - 1; } int segmentNum; @@ -268,10 +276,13 @@ public class DefaultDashChunkSource implements DashChunkSource { && ((InvalidResponseCodeException) e).responseCode == 404) { RepresentationHolder representationHolder = representationHolders[trackSelection.indexOf(chunk.trackFormat)]; - int lastAvailableSegmentNum = representationHolder.getLastSegmentNum(); - if (((MediaChunk) chunk).getNextChunkIndex() > lastAvailableSegmentNum) { - missingLastSegment = true; - return true; + int segmentCount = representationHolder.getSegmentCount(); + if (segmentCount != DashSegmentIndex.INDEX_UNBOUNDED && segmentCount != 0) { + int lastAvailableSegmentNum = representationHolder.getFirstSegmentNum() + segmentCount - 1; + if (((MediaChunk) chunk).getNextChunkIndex() > lastAvailableSegmentNum) { + missingLastSegment = true; + return true; + } } } // Blacklist if appropriate. @@ -405,15 +416,20 @@ public class DefaultDashChunkSource implements DashChunkSource { return; } - int oldIndexLastSegmentNum = oldIndex.getLastSegmentNum(periodDurationUs); + int oldIndexSegmentCount = oldIndex.getSegmentCount(periodDurationUs); + if (oldIndexSegmentCount == 0) { + // Segment numbers cannot shift if the old index was empty. + return; + } + + int oldIndexLastSegmentNum = oldIndex.getFirstSegmentNum() + oldIndexSegmentCount - 1; long oldIndexEndTimeUs = oldIndex.getTimeUs(oldIndexLastSegmentNum) + oldIndex.getDurationUs(oldIndexLastSegmentNum, periodDurationUs); int newIndexFirstSegmentNum = newIndex.getFirstSegmentNum(); long newIndexStartTimeUs = newIndex.getTimeUs(newIndexFirstSegmentNum); if (oldIndexEndTimeUs == newIndexStartTimeUs) { // The new index continues where the old one ended, with no overlap. - segmentNumShift += oldIndex.getLastSegmentNum(periodDurationUs) + 1 - - newIndexFirstSegmentNum; + segmentNumShift += oldIndexLastSegmentNum + 1 - newIndexFirstSegmentNum; } else if (oldIndexEndTimeUs < newIndexStartTimeUs) { // There's a gap between the old index and the new one which means we've slipped behind the // live window and can't proceed. @@ -429,12 +445,8 @@ public class DefaultDashChunkSource implements DashChunkSource { return segmentIndex.getFirstSegmentNum() + segmentNumShift; } - public int getLastSegmentNum() { - int lastSegmentNum = segmentIndex.getLastSegmentNum(periodDurationUs); - if (lastSegmentNum == DashSegmentIndex.INDEX_UNBOUNDED) { - return DashSegmentIndex.INDEX_UNBOUNDED; - } - return lastSegmentNum + segmentNumShift; + public int getSegmentCount() { + return segmentIndex.getSegmentCount(periodDurationUs); } public long getSegmentStartTimeUs(int segmentNum) { diff --git a/library/src/main/java/com/google/android/exoplayer2/source/dash/manifest/Representation.java b/library/src/main/java/com/google/android/exoplayer2/source/dash/manifest/Representation.java index 4146037e1c..5960d4d7ba 100644 --- a/library/src/main/java/com/google/android/exoplayer2/source/dash/manifest/Representation.java +++ b/library/src/main/java/com/google/android/exoplayer2/source/dash/manifest/Representation.java @@ -318,8 +318,8 @@ public abstract class Representation { } @Override - public int getLastSegmentNum(long periodDurationUs) { - return segmentBase.getLastSegmentNum(periodDurationUs); + public int getSegmentCount(long periodDurationUs) { + return segmentBase.getSegmentCount(periodDurationUs); } @Override diff --git a/library/src/main/java/com/google/android/exoplayer2/source/dash/manifest/SegmentBase.java b/library/src/main/java/com/google/android/exoplayer2/source/dash/manifest/SegmentBase.java index 70a65e932a..4f7dc81fc5 100644 --- a/library/src/main/java/com/google/android/exoplayer2/source/dash/manifest/SegmentBase.java +++ b/library/src/main/java/com/google/android/exoplayer2/source/dash/manifest/SegmentBase.java @@ -130,18 +130,22 @@ public abstract class SegmentBase { */ public int getSegmentNum(long timeUs, long periodDurationUs) { final int firstSegmentNum = getFirstSegmentNum(); - int lowIndex = firstSegmentNum; - int highIndex = getLastSegmentNum(periodDurationUs); + final int segmentCount = getSegmentCount(periodDurationUs); + if (segmentCount == 0) { + return firstSegmentNum; + } if (segmentTimeline == null) { // All segments are of equal duration (with the possible exception of the last one). long durationUs = (duration * C.MICROS_PER_SECOND) / timescale; int segmentNum = startNumber + (int) (timeUs / durationUs); // Ensure we stay within bounds. - return segmentNum < lowIndex ? lowIndex - : highIndex != DashSegmentIndex.INDEX_UNBOUNDED && segmentNum > highIndex ? highIndex - : segmentNum; + return segmentNum < firstSegmentNum ? firstSegmentNum + : segmentCount == DashSegmentIndex.INDEX_UNBOUNDED ? segmentNum + : Math.min(segmentNum, firstSegmentNum + segmentCount - 1); } else { - // The high index cannot be unbounded. Identify the segment using binary search. + // The index cannot be unbounded. Identify the segment using binary search. + int lowIndex = firstSegmentNum; + int highIndex = firstSegmentNum + segmentCount - 1; while (lowIndex <= highIndex) { int midIndex = lowIndex + (highIndex - lowIndex) / 2; long midTimeUs = getSegmentTimeUs(midIndex); @@ -165,7 +169,9 @@ public abstract class SegmentBase { long duration = segmentTimeline.get(sequenceNumber - startNumber).duration; return (duration * C.MICROS_PER_SECOND) / timescale; } else { - return sequenceNumber == getLastSegmentNum(periodDurationUs) + int segmentCount = getSegmentCount(periodDurationUs); + return segmentCount != DashSegmentIndex.INDEX_UNBOUNDED + && sequenceNumber == (getFirstSegmentNum() + segmentCount - 1) ? (periodDurationUs - getSegmentTimeUs(sequenceNumber)) : ((duration * C.MICROS_PER_SECOND) / timescale); } @@ -201,9 +207,9 @@ public abstract class SegmentBase { } /** - * @see DashSegmentIndex#getLastSegmentNum(long) + * @see DashSegmentIndex#getSegmentCount(long) */ - public abstract int getLastSegmentNum(long periodDurationUs); + public abstract int getSegmentCount(long periodDurationUs); /** * @see DashSegmentIndex#isExplicit() @@ -250,8 +256,8 @@ public abstract class SegmentBase { } @Override - public int getLastSegmentNum(long periodDurationUs) { - return startNumber + mediaSegments.size() - 1; + public int getSegmentCount(long periodDurationUs) { + return mediaSegments.size(); } @Override @@ -322,14 +328,14 @@ public abstract class SegmentBase { } @Override - public int getLastSegmentNum(long periodDurationUs) { + public int getSegmentCount(long periodDurationUs) { if (segmentTimeline != null) { - return segmentTimeline.size() + startNumber - 1; - } else if (periodDurationUs == C.TIME_UNSET) { - return DashSegmentIndex.INDEX_UNBOUNDED; - } else { + return segmentTimeline.size(); + } else if (periodDurationUs != C.TIME_UNSET) { long durationUs = (duration * C.MICROS_PER_SECOND) / timescale; - return startNumber + (int) Util.ceilDivide(periodDurationUs, durationUs) - 1; + return (int) Util.ceilDivide(periodDurationUs, durationUs); + } else { + return DashSegmentIndex.INDEX_UNBOUNDED; } } diff --git a/library/src/main/java/com/google/android/exoplayer2/source/dash/manifest/SingleSegmentIndex.java b/library/src/main/java/com/google/android/exoplayer2/source/dash/manifest/SingleSegmentIndex.java index 083046d073..4ce49c5ffe 100644 --- a/library/src/main/java/com/google/android/exoplayer2/source/dash/manifest/SingleSegmentIndex.java +++ b/library/src/main/java/com/google/android/exoplayer2/source/dash/manifest/SingleSegmentIndex.java @@ -57,8 +57,8 @@ import com.google.android.exoplayer2.source.dash.DashSegmentIndex; } @Override - public int getLastSegmentNum(long periodDurationUs) { - return 0; + public int getSegmentCount(long periodDurationUs) { + return 1; } @Override