From 1a363849e26553cb6bcfd1297b7a2feffadd16f8 Mon Sep 17 00:00:00 2001 From: Ihor Zakhozhyi Date: Sun, 25 Jan 2015 18:47:16 +0200 Subject: [PATCH 1/8] Fixed wrong calculation of last segment number when using segment template without segment timeline. --- .../java/com/google/android/exoplayer/dash/mpd/SegmentBase.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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..2f9cb9052f 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 @@ -301,7 +301,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; } } From a64df69f8553c64d1e5b421cad81af292cc9ca7b Mon Sep 17 00:00:00 2001 From: Oliver Woodman Date: Mon, 26 Jan 2015 14:08:38 +0000 Subject: [PATCH 2/8] Refine last segment calculation. This makes the calculation correct for the case where periodDurationMs does not divide exactly into durationMs. --- .../java/com/google/android/exoplayer/dash/mpd/SegmentBase.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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 2f9cb9052f..835441bb42 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 @@ -301,7 +301,7 @@ public abstract class SegmentBase { return DashSegmentIndex.INDEX_UNBOUNDED; } else { long durationMs = (duration * 1000) / timescale; - return startNumber + (int) (periodDurationMs / durationMs) - 1; + return startNumber + (int) ((periodDurationMs + durationMs - 1) / durationMs) - 1; } } From f1a7784eb1f74781fe761dd9416aa2b0eeccdf5c Mon Sep 17 00:00:00 2001 From: Oliver Woodman Date: Mon, 26 Jan 2015 14:49:33 +0000 Subject: [PATCH 3/8] 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 --- .../exoplayer/dash/DashChunkSource.java | 11 ++++- .../exoplayer/dash/DashSegmentIndex.java | 19 ++++++++ .../dash/DashWrappingSegmentIndex.java | 5 +++ .../exoplayer/dash/mpd/Representation.java | 5 +++ .../exoplayer/dash/mpd/SegmentBase.java | 45 +++++++++++++++++-- 5 files changed, 79 insertions(+), 6 deletions(-) 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 835441bb42..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; + } + } /** From 76f44eeb137330497820bcc485b5fd905d231311 Mon Sep 17 00:00:00 2001 From: Oliver Woodman Date: Mon, 26 Jan 2015 15:01:00 +0000 Subject: [PATCH 4/8] Test. --- demo/README.md | 3 +++ 1 file changed, 3 insertions(+) create mode 100644 demo/README.md diff --git a/demo/README.md b/demo/README.md new file mode 100644 index 0000000000..5b10a13f98 --- /dev/null +++ b/demo/README.md @@ -0,0 +1,3 @@ +# Test # + +This is a test. \ No newline at end of file From 7b41741db0b03f5e78291e62df2268a9ba32871c Mon Sep 17 00:00:00 2001 From: Oliver Woodman Date: Mon, 26 Jan 2015 15:01:25 +0000 Subject: [PATCH 5/8] Revert test. --- demo/README.md | 3 --- 1 file changed, 3 deletions(-) delete mode 100644 demo/README.md diff --git a/demo/README.md b/demo/README.md deleted file mode 100644 index 5b10a13f98..0000000000 --- a/demo/README.md +++ /dev/null @@ -1,3 +0,0 @@ -# Test # - -This is a test. \ No newline at end of file From fda8f6d35a784068918429d4da5b155b3fa9a1f9 Mon Sep 17 00:00:00 2001 From: Oliver Woodman Date: Mon, 26 Jan 2015 15:12:17 +0000 Subject: [PATCH 6/8] Test --- demo/README.md | 7 +++++++ 1 file changed, 7 insertions(+) create mode 100644 demo/README.md diff --git a/demo/README.md b/demo/README.md new file mode 100644 index 0000000000..1ef15370dc --- /dev/null +++ b/demo/README.md @@ -0,0 +1,7 @@ +# Test2 # + +Link test. + +[rel][] + +[rel](../library) \ No newline at end of file From db5cc21c338b0a44c67e569444ed30af660f5698 Mon Sep 17 00:00:00 2001 From: Oliver Woodman Date: Mon, 26 Jan 2015 15:18:27 +0000 Subject: [PATCH 7/8] Revert test --- demo/README.md | 7 ------- 1 file changed, 7 deletions(-) delete mode 100644 demo/README.md diff --git a/demo/README.md b/demo/README.md deleted file mode 100644 index 1ef15370dc..0000000000 --- a/demo/README.md +++ /dev/null @@ -1,7 +0,0 @@ -# Test2 # - -Link test. - -[rel][] - -[rel](../library) \ No newline at end of file From ce2f681bd342f3d84479ce625efceaafb3c216cc Mon Sep 17 00:00:00 2001 From: Oliver Woodman Date: Mon, 26 Jan 2015 16:18:41 +0000 Subject: [PATCH 8/8] Add directories for extensions + additional demos --- demo/README.md | 5 +++++ demo_misc/README.md | 7 +++++++ extensions/README.md | 3 +++ 3 files changed, 15 insertions(+) create mode 100644 demo/README.md create mode 100644 demo_misc/README.md create mode 100644 extensions/README.md 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.