Commit f1a7784e by Oliver Woodman

Fix DASH Live edge calculation.

Also added clamping to getSegmentNum in one case where
it was not already implemented, and defined this behavior
property in the getSegmentNum javadoc.

Issue: #262
parent a64df69f
...@@ -349,7 +349,7 @@ public class DashChunkSource implements ChunkSource { ...@@ -349,7 +349,7 @@ public class DashChunkSource implements ChunkSource {
int segmentNum; int segmentNum;
if (queue.isEmpty()) { if (queue.isEmpty()) {
if (currentManifest.dynamic) { if (currentManifest.dynamic) {
seekPositionUs = getLiveSeekPosition(nowUs, indexUnbounded); seekPositionUs = getLiveSeekPosition(nowUs, indexUnbounded, segmentIndex.isExplicit());
} }
segmentNum = segmentIndex.getSegmentNum(seekPositionUs); segmentNum = segmentIndex.getSegmentNum(seekPositionUs);
} else { } else {
...@@ -476,9 +476,10 @@ public class DashChunkSource implements ChunkSource { ...@@ -476,9 +476,10 @@ public class DashChunkSource implements ChunkSource {
* *
* @param nowUs An estimate of the current server time, in microseconds. * @param nowUs An estimate of the current server time, in microseconds.
* @param indexUnbounded True if the segment index for this source is unbounded. False otherwise. * @param indexUnbounded True if the segment index for this source is unbounded. False otherwise.
* @param indexExplicit True if the segment index is explicit. False otherwise.
* @return The seek position in microseconds. * @return The seek position in microseconds.
*/ */
private long getLiveSeekPosition(long nowUs, boolean indexUnbounded) { private long getLiveSeekPosition(long nowUs, boolean indexUnbounded, boolean indexExplicit) {
long liveEdgeTimestampUs; long liveEdgeTimestampUs;
if (indexUnbounded) { if (indexUnbounded) {
liveEdgeTimestampUs = nowUs - currentManifest.availabilityStartTime * 1000; liveEdgeTimestampUs = nowUs - currentManifest.availabilityStartTime * 1000;
...@@ -491,6 +492,12 @@ public class DashChunkSource implements ChunkSource { ...@@ -491,6 +492,12 @@ public class DashChunkSource implements ChunkSource {
+ segmentIndex.getDurationUs(lastSegmentNum); + segmentIndex.getDurationUs(lastSegmentNum);
liveEdgeTimestampUs = Math.max(liveEdgeTimestampUs, indexLiveEdgeTimestampUs); liveEdgeTimestampUs = Math.max(liveEdgeTimestampUs, indexLiveEdgeTimestampUs);
} }
if (!indexExplicit) {
// Some segments defined by the index may not be available yet. Bound the calculated live
// edge based on the elapsed time since the manifest became available.
liveEdgeTimestampUs = Math.min(liveEdgeTimestampUs,
nowUs - currentManifest.availabilityStartTime * 1000);
}
} }
return liveEdgeTimestampUs - liveEdgeLatencyUs; return liveEdgeTimestampUs - liveEdgeLatencyUs;
} }
......
...@@ -28,6 +28,11 @@ public interface DashSegmentIndex { ...@@ -28,6 +28,11 @@ public interface DashSegmentIndex {
/** /**
* Returns the segment number of the segment containing a given media time. * Returns the segment number of the segment containing a given media time.
* <p>
* 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()} (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.
* @return The segment number of the corresponding segment. * @return The segment number of the corresponding segment.
...@@ -78,4 +83,18 @@ public interface DashSegmentIndex { ...@@ -78,4 +83,18 @@ public interface DashSegmentIndex {
*/ */
int getLastSegmentNum(); int getLastSegmentNum();
/**
* Returns true if segments are defined explicitly by the index.
* <p>
* If true is returned, each segment is defined explicitly by the index data, and all of the
* listed segments are guaranteed to be available at the time when the index was obtained.
* <p>
* If false is returned then segment information was derived from properties such as a fixed
* segment duration. If the presentation is dynamic, it's possible that only a subset of the
* segments are available.
*
* @return True if segments are defined explicitly by the index. False otherwise.
*/
boolean isExplicit();
} }
...@@ -74,4 +74,9 @@ public class DashWrappingSegmentIndex implements DashSegmentIndex { ...@@ -74,4 +74,9 @@ public class DashWrappingSegmentIndex implements DashSegmentIndex {
return Util.binarySearchFloor(segmentIndex.timesUs, timeUs, true, true); return Util.binarySearchFloor(segmentIndex.timesUs, timeUs, true, true);
} }
@Override
public boolean isExplicit() {
return true;
}
} }
...@@ -277,6 +277,11 @@ public abstract class Representation { ...@@ -277,6 +277,11 @@ public abstract class Representation {
return segmentBase.getLastSegmentNum(); return segmentBase.getLastSegmentNum();
} }
@Override
public boolean isExplicit() {
return segmentBase.isExplicit();
}
} }
} }
...@@ -128,15 +128,22 @@ public abstract class SegmentBase { ...@@ -128,15 +128,22 @@ public abstract class SegmentBase {
this.segmentTimeline = segmentTimeline; this.segmentTimeline = segmentTimeline;
} }
/**
* @see DashSegmentIndex#getSegmentNum(long)
*/
public int getSegmentNum(long timeUs) { public int getSegmentNum(long timeUs) {
int lowIndex = getFirstSegmentNum();
int highIndex = getLastSegmentNum();
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;
return startNumber + (int) (timeUs / durationUs); int segmentNum = startNumber + (int) (timeUs / durationUs);
// Ensure we stay within bounds.
return segmentNum < lowIndex ? lowIndex
: highIndex != DashSegmentIndex.INDEX_UNBOUNDED && segmentNum > highIndex ? highIndex
: segmentNum;
} else { } else {
// Identify the segment using binary search. // The high index cannot be unbounded. Identify the segment using binary search.
int lowIndex = getFirstSegmentNum();
int highIndex = getLastSegmentNum();
while (lowIndex <= highIndex) { while (lowIndex <= highIndex) {
int midIndex = (lowIndex + highIndex) / 2; int midIndex = (lowIndex + highIndex) / 2;
long midTimeUs = getSegmentTimeUs(midIndex); long midTimeUs = getSegmentTimeUs(midIndex);
...@@ -152,6 +159,9 @@ public abstract class SegmentBase { ...@@ -152,6 +159,9 @@ public abstract class SegmentBase {
} }
} }
/**
* @see DashSegmentIndex#getDurationUs(int)
*/
public final long getSegmentDurationUs(int sequenceNumber) { public final long getSegmentDurationUs(int sequenceNumber) {
if (segmentTimeline != null) { if (segmentTimeline != null) {
long duration = segmentTimeline.get(sequenceNumber - startNumber).duration; long duration = segmentTimeline.get(sequenceNumber - startNumber).duration;
...@@ -163,6 +173,9 @@ public abstract class SegmentBase { ...@@ -163,6 +173,9 @@ public abstract class SegmentBase {
} }
} }
/**
* @see DashSegmentIndex#getTimeUs(int)
*/
public final long getSegmentTimeUs(int sequenceNumber) { public final long getSegmentTimeUs(int sequenceNumber) {
long unscaledSegmentTime; long unscaledSegmentTime;
if (segmentTimeline != null) { if (segmentTimeline != null) {
...@@ -174,14 +187,33 @@ public abstract class SegmentBase { ...@@ -174,14 +187,33 @@ public abstract class SegmentBase {
return Util.scaleLargeTimestamp(unscaledSegmentTime, C.MICROS_PER_SECOND, timescale); return Util.scaleLargeTimestamp(unscaledSegmentTime, C.MICROS_PER_SECOND, timescale);
} }
/**
* Returns a {@link RangedUri} defining the location of a segment for the given index in the
* given representation.
*
* @see DashSegmentIndex#getSegmentUrl(int)
*/
public abstract RangedUri getSegmentUrl(Representation representation, int index); public abstract RangedUri getSegmentUrl(Representation representation, int index);
/**
* @see DashSegmentIndex#getFirstSegmentNum()
*/
public int getFirstSegmentNum() { public int getFirstSegmentNum() {
return startNumber; return startNumber;
} }
/**
* @see DashSegmentIndex#getLastSegmentNum()
*/
public abstract int getLastSegmentNum(); public abstract int getLastSegmentNum();
/**
* @see DashSegmentIndex#isExplicit()
*/
public boolean isExplicit() {
return segmentTimeline != null;
}
} }
/** /**
...@@ -225,6 +257,11 @@ public abstract class SegmentBase { ...@@ -225,6 +257,11 @@ public abstract class SegmentBase {
return startNumber + mediaSegments.size() - 1; return startNumber + mediaSegments.size() - 1;
} }
@Override
public boolean isExplicit() {
return true;
}
} }
/** /**
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or sign in to comment