mirror of
https://github.com/androidx/media.git
synced 2025-05-10 00:59:51 +08:00
DASH: Correctly handle empty segment indices
Issue: #1865 ------------- Created by MOE: https://github.com/google/moe MOE_MIGRATED_REVID=147613244
This commit is contained in:
parent
5c571e6e9d
commit
d6e15b7953
@ -572,22 +572,28 @@ public final class DashMediaSource implements MediaSource {
|
|||||||
long availableStartTimeUs = 0;
|
long availableStartTimeUs = 0;
|
||||||
long availableEndTimeUs = Long.MAX_VALUE;
|
long availableEndTimeUs = Long.MAX_VALUE;
|
||||||
boolean isIndexExplicit = false;
|
boolean isIndexExplicit = false;
|
||||||
|
boolean seenEmptyIndex = false;
|
||||||
for (int i = 0; i < adaptationSetCount; i++) {
|
for (int i = 0; i < adaptationSetCount; i++) {
|
||||||
DashSegmentIndex index = period.adaptationSets.get(i).representations.get(0).getIndex();
|
DashSegmentIndex index = period.adaptationSets.get(i).representations.get(0).getIndex();
|
||||||
if (index == null) {
|
if (index == null) {
|
||||||
return new PeriodSeekInfo(true, 0, durationUs);
|
return new PeriodSeekInfo(true, 0, durationUs);
|
||||||
}
|
}
|
||||||
int firstSegmentNum = index.getFirstSegmentNum();
|
|
||||||
int lastSegmentNum = index.getLastSegmentNum(durationUs);
|
|
||||||
isIndexExplicit |= index.isExplicit();
|
isIndexExplicit |= index.isExplicit();
|
||||||
|
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);
|
long adaptationSetAvailableStartTimeUs = index.getTimeUs(firstSegmentNum);
|
||||||
availableStartTimeUs = Math.max(availableStartTimeUs, adaptationSetAvailableStartTimeUs);
|
availableStartTimeUs = Math.max(availableStartTimeUs, adaptationSetAvailableStartTimeUs);
|
||||||
if (lastSegmentNum != DashSegmentIndex.INDEX_UNBOUNDED) {
|
if (segmentCount != DashSegmentIndex.INDEX_UNBOUNDED) {
|
||||||
|
int lastSegmentNum = firstSegmentNum + segmentCount - 1;
|
||||||
long adaptationSetAvailableEndTimeUs = index.getTimeUs(lastSegmentNum)
|
long adaptationSetAvailableEndTimeUs = index.getTimeUs(lastSegmentNum)
|
||||||
+ index.getDurationUs(lastSegmentNum, durationUs);
|
+ index.getDurationUs(lastSegmentNum, durationUs);
|
||||||
availableEndTimeUs = Math.min(availableEndTimeUs, adaptationSetAvailableEndTimeUs);
|
availableEndTimeUs = Math.min(availableEndTimeUs, adaptationSetAvailableEndTimeUs);
|
||||||
} else {
|
}
|
||||||
// The available end time is unmodified, because this index is unbounded.
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return new PeriodSeekInfo(isIndexExplicit, availableStartTimeUs, availableEndTimeUs);
|
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.
|
// not correspond to the start of a segment in both, but this is an edge case.
|
||||||
DashSegmentIndex snapIndex = period.adaptationSets.get(videoAdaptationSetIndex)
|
DashSegmentIndex snapIndex = period.adaptationSets.get(videoAdaptationSetIndex)
|
||||||
.representations.get(0).getIndex();
|
.representations.get(0).getIndex();
|
||||||
if (snapIndex == null) {
|
if (snapIndex == null || snapIndex.getSegmentCount(periodDurationUs) == 0) {
|
||||||
// Video adaptation set does not include an index for snapping.
|
// Video adaptation set does not include a non-empty index for snapping.
|
||||||
return windowDefaultStartPositionUs;
|
return windowDefaultStartPositionUs;
|
||||||
}
|
}
|
||||||
int segmentNum = snapIndex.getSegmentNum(defaultStartPositionInPeriodUs, periodDurationUs);
|
int segmentNum = snapIndex.getSegmentNum(defaultStartPositionInPeriodUs, periodDurationUs);
|
||||||
|
@ -26,12 +26,10 @@ public interface DashSegmentIndex {
|
|||||||
int INDEX_UNBOUNDED = -1;
|
int INDEX_UNBOUNDED = -1;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns the segment number of the segment containing a given media time.
|
* Returns {@code getFirstSegmentNum()} if the index has no segments or if the given media time is
|
||||||
* <p>
|
* earlier than the start of the first segment. Returns {@code getFirstSegmentNum() +
|
||||||
* If the given media time is outside the range of the index, then the returned segment number is
|
* getSegmentCount() - 1} if the given media time is later than the end of the last segment.
|
||||||
* clamped to {@link #getFirstSegmentNum()} (if the given media time is earlier the start of the
|
* Otherwise, returns the segment number of the segment containing the given media time.
|
||||||
* first segment) or {@link #getLastSegmentNum(long)} (if the given media time is later then the
|
|
||||||
* end of the last segment).
|
|
||||||
*
|
*
|
||||||
* @param timeUs The time in microseconds.
|
* @param timeUs The time in microseconds.
|
||||||
* @param periodDurationUs The duration of the enclosing period in microseconds, or
|
* @param periodDurationUs The duration of the enclosing period in microseconds, or
|
||||||
@ -74,7 +72,7 @@ public interface DashSegmentIndex {
|
|||||||
int getFirstSegmentNum();
|
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}.
|
||||||
* <p>
|
* <p>
|
||||||
* An unbounded index occurs if a dynamic manifest uses SegmentTemplate elements without a
|
* 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
|
* 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
|
* @param periodDurationUs The duration of the enclosing period in microseconds, or
|
||||||
* {@link C#TIME_UNSET} if the period's duration is not yet known.
|
* {@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.
|
* Returns true if segments are defined explicitly by the index.
|
||||||
|
@ -39,8 +39,8 @@ import com.google.android.exoplayer2.source.dash.manifest.RangedUri;
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public int getLastSegmentNum(long periodDurationUs) {
|
public int getSegmentCount(long periodDurationUs) {
|
||||||
return chunkIndex.length - 1;
|
return chunkIndex.length;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -194,10 +194,16 @@ public class DefaultDashChunkSource implements DashChunkSource {
|
|||||||
}
|
}
|
||||||
|
|
||||||
long nowUs = getNowUnixTimeUs();
|
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 firstAvailableSegmentNum = representationHolder.getFirstSegmentNum();
|
||||||
int lastAvailableSegmentNum = representationHolder.getLastSegmentNum();
|
int lastAvailableSegmentNum;
|
||||||
boolean indexUnbounded = lastAvailableSegmentNum == DashSegmentIndex.INDEX_UNBOUNDED;
|
if (availableSegmentCount == DashSegmentIndex.INDEX_UNBOUNDED) {
|
||||||
if (indexUnbounded) {
|
|
||||||
// The index is itself unbounded. We need to use the current time to calculate the range of
|
// The index is itself unbounded. We need to use the current time to calculate the range of
|
||||||
// available segments.
|
// available segments.
|
||||||
long liveEdgeTimeUs = nowUs - manifest.availabilityStartTime * 1000;
|
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
|
// getSegmentNum(liveEdgeTimestampUs) will not be completed yet, so subtract one to get the
|
||||||
// index of the last completed segment.
|
// index of the last completed segment.
|
||||||
lastAvailableSegmentNum = representationHolder.getSegmentNum(liveEdgeTimeInPeriodUs) - 1;
|
lastAvailableSegmentNum = representationHolder.getSegmentNum(liveEdgeTimeInPeriodUs) - 1;
|
||||||
|
} else {
|
||||||
|
lastAvailableSegmentNum = firstAvailableSegmentNum + availableSegmentCount - 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
int segmentNum;
|
int segmentNum;
|
||||||
@ -268,12 +276,15 @@ public class DefaultDashChunkSource implements DashChunkSource {
|
|||||||
&& ((InvalidResponseCodeException) e).responseCode == 404) {
|
&& ((InvalidResponseCodeException) e).responseCode == 404) {
|
||||||
RepresentationHolder representationHolder =
|
RepresentationHolder representationHolder =
|
||||||
representationHolders[trackSelection.indexOf(chunk.trackFormat)];
|
representationHolders[trackSelection.indexOf(chunk.trackFormat)];
|
||||||
int lastAvailableSegmentNum = representationHolder.getLastSegmentNum();
|
int segmentCount = representationHolder.getSegmentCount();
|
||||||
|
if (segmentCount != DashSegmentIndex.INDEX_UNBOUNDED && segmentCount != 0) {
|
||||||
|
int lastAvailableSegmentNum = representationHolder.getFirstSegmentNum() + segmentCount - 1;
|
||||||
if (((MediaChunk) chunk).getNextChunkIndex() > lastAvailableSegmentNum) {
|
if (((MediaChunk) chunk).getNextChunkIndex() > lastAvailableSegmentNum) {
|
||||||
missingLastSegment = true;
|
missingLastSegment = true;
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
// Blacklist if appropriate.
|
// Blacklist if appropriate.
|
||||||
return ChunkedTrackBlacklistUtil.maybeBlacklistTrack(trackSelection,
|
return ChunkedTrackBlacklistUtil.maybeBlacklistTrack(trackSelection,
|
||||||
trackSelection.indexOf(chunk.trackFormat), e);
|
trackSelection.indexOf(chunk.trackFormat), e);
|
||||||
@ -405,15 +416,20 @@ public class DefaultDashChunkSource implements DashChunkSource {
|
|||||||
return;
|
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)
|
long oldIndexEndTimeUs = oldIndex.getTimeUs(oldIndexLastSegmentNum)
|
||||||
+ oldIndex.getDurationUs(oldIndexLastSegmentNum, periodDurationUs);
|
+ oldIndex.getDurationUs(oldIndexLastSegmentNum, periodDurationUs);
|
||||||
int newIndexFirstSegmentNum = newIndex.getFirstSegmentNum();
|
int newIndexFirstSegmentNum = newIndex.getFirstSegmentNum();
|
||||||
long newIndexStartTimeUs = newIndex.getTimeUs(newIndexFirstSegmentNum);
|
long newIndexStartTimeUs = newIndex.getTimeUs(newIndexFirstSegmentNum);
|
||||||
if (oldIndexEndTimeUs == newIndexStartTimeUs) {
|
if (oldIndexEndTimeUs == newIndexStartTimeUs) {
|
||||||
// The new index continues where the old one ended, with no overlap.
|
// The new index continues where the old one ended, with no overlap.
|
||||||
segmentNumShift += oldIndex.getLastSegmentNum(periodDurationUs) + 1
|
segmentNumShift += oldIndexLastSegmentNum + 1 - newIndexFirstSegmentNum;
|
||||||
- newIndexFirstSegmentNum;
|
|
||||||
} else if (oldIndexEndTimeUs < newIndexStartTimeUs) {
|
} else if (oldIndexEndTimeUs < newIndexStartTimeUs) {
|
||||||
// There's a gap between the old index and the new one which means we've slipped behind the
|
// 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.
|
// live window and can't proceed.
|
||||||
@ -429,12 +445,8 @@ public class DefaultDashChunkSource implements DashChunkSource {
|
|||||||
return segmentIndex.getFirstSegmentNum() + segmentNumShift;
|
return segmentIndex.getFirstSegmentNum() + segmentNumShift;
|
||||||
}
|
}
|
||||||
|
|
||||||
public int getLastSegmentNum() {
|
public int getSegmentCount() {
|
||||||
int lastSegmentNum = segmentIndex.getLastSegmentNum(periodDurationUs);
|
return segmentIndex.getSegmentCount(periodDurationUs);
|
||||||
if (lastSegmentNum == DashSegmentIndex.INDEX_UNBOUNDED) {
|
|
||||||
return DashSegmentIndex.INDEX_UNBOUNDED;
|
|
||||||
}
|
|
||||||
return lastSegmentNum + segmentNumShift;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public long getSegmentStartTimeUs(int segmentNum) {
|
public long getSegmentStartTimeUs(int segmentNum) {
|
||||||
|
@ -318,8 +318,8 @@ public abstract class Representation {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public int getLastSegmentNum(long periodDurationUs) {
|
public int getSegmentCount(long periodDurationUs) {
|
||||||
return segmentBase.getLastSegmentNum(periodDurationUs);
|
return segmentBase.getSegmentCount(periodDurationUs);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -130,18 +130,22 @@ public abstract class SegmentBase {
|
|||||||
*/
|
*/
|
||||||
public int getSegmentNum(long timeUs, long periodDurationUs) {
|
public int getSegmentNum(long timeUs, long periodDurationUs) {
|
||||||
final int firstSegmentNum = getFirstSegmentNum();
|
final int firstSegmentNum = getFirstSegmentNum();
|
||||||
int lowIndex = firstSegmentNum;
|
final int segmentCount = getSegmentCount(periodDurationUs);
|
||||||
int highIndex = getLastSegmentNum(periodDurationUs);
|
if (segmentCount == 0) {
|
||||||
|
return firstSegmentNum;
|
||||||
|
}
|
||||||
if (segmentTimeline == null) {
|
if (segmentTimeline == null) {
|
||||||
// All segments are of equal duration (with the possible exception of the last one).
|
// All segments are of equal duration (with the possible exception of the last one).
|
||||||
long durationUs = (duration * C.MICROS_PER_SECOND) / timescale;
|
long durationUs = (duration * C.MICROS_PER_SECOND) / timescale;
|
||||||
int segmentNum = startNumber + (int) (timeUs / durationUs);
|
int segmentNum = startNumber + (int) (timeUs / durationUs);
|
||||||
// Ensure we stay within bounds.
|
// Ensure we stay within bounds.
|
||||||
return segmentNum < lowIndex ? lowIndex
|
return segmentNum < firstSegmentNum ? firstSegmentNum
|
||||||
: highIndex != DashSegmentIndex.INDEX_UNBOUNDED && segmentNum > highIndex ? highIndex
|
: segmentCount == DashSegmentIndex.INDEX_UNBOUNDED ? segmentNum
|
||||||
: segmentNum;
|
: Math.min(segmentNum, firstSegmentNum + segmentCount - 1);
|
||||||
} else {
|
} 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) {
|
while (lowIndex <= highIndex) {
|
||||||
int midIndex = lowIndex + (highIndex - lowIndex) / 2;
|
int midIndex = lowIndex + (highIndex - lowIndex) / 2;
|
||||||
long midTimeUs = getSegmentTimeUs(midIndex);
|
long midTimeUs = getSegmentTimeUs(midIndex);
|
||||||
@ -165,7 +169,9 @@ public abstract class SegmentBase {
|
|||||||
long duration = segmentTimeline.get(sequenceNumber - startNumber).duration;
|
long duration = segmentTimeline.get(sequenceNumber - startNumber).duration;
|
||||||
return (duration * C.MICROS_PER_SECOND) / timescale;
|
return (duration * C.MICROS_PER_SECOND) / timescale;
|
||||||
} else {
|
} else {
|
||||||
return sequenceNumber == getLastSegmentNum(periodDurationUs)
|
int segmentCount = getSegmentCount(periodDurationUs);
|
||||||
|
return segmentCount != DashSegmentIndex.INDEX_UNBOUNDED
|
||||||
|
&& sequenceNumber == (getFirstSegmentNum() + segmentCount - 1)
|
||||||
? (periodDurationUs - getSegmentTimeUs(sequenceNumber))
|
? (periodDurationUs - getSegmentTimeUs(sequenceNumber))
|
||||||
: ((duration * C.MICROS_PER_SECOND) / timescale);
|
: ((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()
|
* @see DashSegmentIndex#isExplicit()
|
||||||
@ -250,8 +256,8 @@ public abstract class SegmentBase {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public int getLastSegmentNum(long periodDurationUs) {
|
public int getSegmentCount(long periodDurationUs) {
|
||||||
return startNumber + mediaSegments.size() - 1;
|
return mediaSegments.size();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@ -322,14 +328,14 @@ public abstract class SegmentBase {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public int getLastSegmentNum(long periodDurationUs) {
|
public int getSegmentCount(long periodDurationUs) {
|
||||||
if (segmentTimeline != null) {
|
if (segmentTimeline != null) {
|
||||||
return segmentTimeline.size() + startNumber - 1;
|
return segmentTimeline.size();
|
||||||
} else if (periodDurationUs == C.TIME_UNSET) {
|
} else if (periodDurationUs != C.TIME_UNSET) {
|
||||||
return DashSegmentIndex.INDEX_UNBOUNDED;
|
|
||||||
} else {
|
|
||||||
long durationUs = (duration * C.MICROS_PER_SECOND) / timescale;
|
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;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -57,8 +57,8 @@ import com.google.android.exoplayer2.source.dash.DashSegmentIndex;
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public int getLastSegmentNum(long periodDurationUs) {
|
public int getSegmentCount(long periodDurationUs) {
|
||||||
return 0;
|
return 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
Loading…
x
Reference in New Issue
Block a user