commit
c6c6f2d83e
5
demo/README.md
Normal file
5
demo/README.md
Normal 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
7
demo_misc/README.md
Normal 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
3
extensions/README.md
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
# Extensions #
|
||||||
|
|
||||||
|
This folder contains optional ExoPlayer extensions.
|
@ -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;
|
||||||
}
|
}
|
||||||
|
@ -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();
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -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 {
|
|||||||
return segmentBase.getLastSegmentNum();
|
return segmentBase.getLastSegmentNum();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isExplicit() {
|
||||||
|
return segmentBase.isExplicit();
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -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;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Loading…
x
Reference in New Issue
Block a user