Merge pull request #266 from google/dev

dev -> dev-hls
This commit is contained in:
ojw28 2015-01-27 15:10:34 +00:00
commit c6c6f2d83e
8 changed files with 95 additions and 7 deletions

5
demo/README.md Normal file
View File

@ -0,0 +1,5 @@
# Demo application #
This folder contains a demo application that uses ExoPlayer to play a number
of test streams. It can be used as a starting point or reference project when
developing other applications that make use of the ExoPlayer library.

7
demo_misc/README.md Normal file
View File

@ -0,0 +1,7 @@
# Miscellaneous demos #
This folder contains miscellaneous demo applications. For example applications
that demonstrate use of optional extensions, or more advanced features.
A general purpose ExoPlayer demo application can be found in the [demo](../demo)
folder.

3
extensions/README.md Normal file
View File

@ -0,0 +1,3 @@
# Extensions #
This folder contains optional ExoPlayer extensions.

View File

@ -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 {
* *
* @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 {
+ 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;
} }

View File

@ -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 {
*/ */
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();
} }

View File

@ -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;
}
} }

View File

@ -277,6 +277,11 @@ public abstract class Representation {
return segmentBase.getLastSegmentNum(); return segmentBase.getLastSegmentNum();
} }
@Override
public boolean isExplicit() {
return segmentBase.isExplicit();
}
} }
} }

View File

@ -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 {
} }
} }
/**
* @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 {
} }
} }
/**
* @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 {
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 {
return startNumber + mediaSegments.size() - 1; return startNumber + mediaSegments.size() - 1;
} }
@Override
public boolean isExplicit() {
return true;
}
} }
/** /**
@ -301,7 +338,7 @@ public abstract class SegmentBase {
return DashSegmentIndex.INDEX_UNBOUNDED; return DashSegmentIndex.INDEX_UNBOUNDED;
} else { } else {
long durationMs = (duration * 1000) / timescale; long durationMs = (duration * 1000) / timescale;
return startNumber + (int) (periodDurationMs / durationMs); return startNumber + (int) ((periodDurationMs + durationMs - 1) / durationMs) - 1;
} }
} }