diff --git a/demo/README.md b/demo/README.md new file mode 100644 index 0000000000..ca37392623 --- /dev/null +++ b/demo/README.md @@ -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. diff --git a/demo_misc/README.md b/demo_misc/README.md new file mode 100644 index 0000000000..f7d7af6ac4 --- /dev/null +++ b/demo_misc/README.md @@ -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. diff --git a/extensions/README.md b/extensions/README.md new file mode 100644 index 0000000000..5bb863c13e --- /dev/null +++ b/extensions/README.md @@ -0,0 +1,3 @@ +# Extensions # + +This folder contains optional ExoPlayer extensions. diff --git a/library/src/main/java/com/google/android/exoplayer/dash/DashChunkSource.java b/library/src/main/java/com/google/android/exoplayer/dash/DashChunkSource.java index a7bc566889..95e62d4b15 100644 --- a/library/src/main/java/com/google/android/exoplayer/dash/DashChunkSource.java +++ b/library/src/main/java/com/google/android/exoplayer/dash/DashChunkSource.java @@ -349,7 +349,7 @@ public class DashChunkSource implements ChunkSource { int segmentNum; if (queue.isEmpty()) { if (currentManifest.dynamic) { - seekPositionUs = getLiveSeekPosition(nowUs, indexUnbounded); + seekPositionUs = getLiveSeekPosition(nowUs, indexUnbounded, segmentIndex.isExplicit()); } segmentNum = segmentIndex.getSegmentNum(seekPositionUs); } else { @@ -476,9 +476,10 @@ public class DashChunkSource implements ChunkSource { * * @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 indexExplicit True if the segment index is explicit. False otherwise. * @return The seek position in microseconds. */ - private long getLiveSeekPosition(long nowUs, boolean indexUnbounded) { + private long getLiveSeekPosition(long nowUs, boolean indexUnbounded, boolean indexExplicit) { long liveEdgeTimestampUs; if (indexUnbounded) { liveEdgeTimestampUs = nowUs - currentManifest.availabilityStartTime * 1000; @@ -491,6 +492,12 @@ public class DashChunkSource implements ChunkSource { + segmentIndex.getDurationUs(lastSegmentNum); 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; } diff --git a/library/src/main/java/com/google/android/exoplayer/dash/DashSegmentIndex.java b/library/src/main/java/com/google/android/exoplayer/dash/DashSegmentIndex.java index e66aa38380..e922757e13 100644 --- a/library/src/main/java/com/google/android/exoplayer/dash/DashSegmentIndex.java +++ b/library/src/main/java/com/google/android/exoplayer/dash/DashSegmentIndex.java @@ -28,6 +28,11 @@ public interface DashSegmentIndex { /** * 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()} (if the given media time is later then the end + * of the last segment). * * @param timeUs The time in microseconds. * @return The segment number of the corresponding segment. @@ -78,4 +83,18 @@ public interface DashSegmentIndex { */ int getLastSegmentNum(); + /** + * Returns true if segments are defined explicitly by the index. + *
+ * 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. + *
+ * 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(); + } diff --git a/library/src/main/java/com/google/android/exoplayer/dash/DashWrappingSegmentIndex.java b/library/src/main/java/com/google/android/exoplayer/dash/DashWrappingSegmentIndex.java index 3d0bb0913a..5d62a58812 100644 --- a/library/src/main/java/com/google/android/exoplayer/dash/DashWrappingSegmentIndex.java +++ b/library/src/main/java/com/google/android/exoplayer/dash/DashWrappingSegmentIndex.java @@ -74,4 +74,9 @@ public class DashWrappingSegmentIndex implements DashSegmentIndex { return Util.binarySearchFloor(segmentIndex.timesUs, timeUs, true, true); } + @Override + public boolean isExplicit() { + return true; + } + } diff --git a/library/src/main/java/com/google/android/exoplayer/dash/mpd/Representation.java b/library/src/main/java/com/google/android/exoplayer/dash/mpd/Representation.java index d089ba7f59..fa58775f27 100644 --- a/library/src/main/java/com/google/android/exoplayer/dash/mpd/Representation.java +++ b/library/src/main/java/com/google/android/exoplayer/dash/mpd/Representation.java @@ -277,6 +277,11 @@ public abstract class Representation { return segmentBase.getLastSegmentNum(); } + @Override + public boolean isExplicit() { + return segmentBase.isExplicit(); + } + } } diff --git a/library/src/main/java/com/google/android/exoplayer/dash/mpd/SegmentBase.java b/library/src/main/java/com/google/android/exoplayer/dash/mpd/SegmentBase.java index 91093980c6..ab12ff2b9c 100644 --- a/library/src/main/java/com/google/android/exoplayer/dash/mpd/SegmentBase.java +++ b/library/src/main/java/com/google/android/exoplayer/dash/mpd/SegmentBase.java @@ -128,15 +128,22 @@ public abstract class SegmentBase { this.segmentTimeline = segmentTimeline; } + /** + * @see DashSegmentIndex#getSegmentNum(long) + */ public int getSegmentNum(long timeUs) { + int lowIndex = getFirstSegmentNum(); + int highIndex = getLastSegmentNum(); 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; - 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 { - // Identify the segment using binary search. - int lowIndex = getFirstSegmentNum(); - int highIndex = getLastSegmentNum(); + // The high index cannot be unbounded. Identify the segment using binary search. while (lowIndex <= highIndex) { int midIndex = (lowIndex + highIndex) / 2; long midTimeUs = getSegmentTimeUs(midIndex); @@ -152,6 +159,9 @@ public abstract class SegmentBase { } } + /** + * @see DashSegmentIndex#getDurationUs(int) + */ public final long getSegmentDurationUs(int sequenceNumber) { if (segmentTimeline != null) { 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) { long unscaledSegmentTime; if (segmentTimeline != null) { @@ -174,14 +187,33 @@ public abstract class SegmentBase { 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); + /** + * @see DashSegmentIndex#getFirstSegmentNum() + */ public int getFirstSegmentNum() { return startNumber; } + /** + * @see DashSegmentIndex#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; } + @Override + public boolean isExplicit() { + return true; + } + } /** @@ -301,7 +338,7 @@ public abstract class SegmentBase { return DashSegmentIndex.INDEX_UNBOUNDED; } else { long durationMs = (duration * 1000) / timescale; - return startNumber + (int) (periodDurationMs / durationMs); + return startNumber + (int) ((periodDurationMs + durationMs - 1) / durationMs) - 1; } }