diff --git a/library/dash/src/main/java/com/google/android/exoplayer2/source/dash/DashSegmentIndex.java b/library/dash/src/main/java/com/google/android/exoplayer2/source/dash/DashSegmentIndex.java
index 9d45bc726e..3f95d8c5a1 100644
--- a/library/dash/src/main/java/com/google/android/exoplayer2/source/dash/DashSegmentIndex.java
+++ b/library/dash/src/main/java/com/google/android/exoplayer2/source/dash/DashSegmentIndex.java
@@ -64,38 +64,54 @@ public interface DashSegmentIndex {
*/
RangedUri getSegmentUrl(long segmentNum);
- /**
- * Returns the segment number of the first segment.
- *
- * @return The segment number of the first segment.
- */
+ /** Returns the segment number of the first defined segment in the index. */
long getFirstSegmentNum();
/**
- * Returns the number of segments in the index, or {@link #INDEX_UNBOUNDED}.
- *
- * An unbounded index occurs if a dynamic manifest uses SegmentTemplate elements without a
- * SegmentTimeline element, and if the period duration is not yet known. In this case the caller
- * must manually determine the window of currently available segments.
+ * Returns the segment number of the first available segment in the index.
*
- * @param periodDurationUs The duration of the enclosing period in microseconds, or
- * {@link C#TIME_UNSET} if the period's duration is not yet known.
+ * @param periodDurationUs The duration of the enclosing period in microseconds, or {@link
+ * C#TIME_UNSET} if the period's duration is not yet known.
+ * @param nowUnixTimeUs The current time in milliseconds since the Unix epoch.
+ * @return The number of the first available segment.
+ */
+ long getFirstAvailableSegmentNum(long periodDurationUs, long nowUnixTimeUs);
+
+ /**
+ * Returns the number of segments defined in the index, or {@link #INDEX_UNBOUNDED}.
+ *
+ *
An unbounded index occurs if a dynamic manifest uses SegmentTemplate elements without a
+ * SegmentTimeline element, and if the period duration is not yet known. In this case the caller
+ * can query the available segment using {@link #getFirstAvailableSegmentNum(long, long)} and
+ * {@link #getAvailableSegmentCount(long, long)}.
+ *
+ * @param periodDurationUs The duration of the enclosing period in microseconds, or {@link
+ * C#TIME_UNSET} if the period's duration is not yet known.
* @return The number of segments in the index, or {@link #INDEX_UNBOUNDED}.
*/
int getSegmentCount(long periodDurationUs);
+ /**
+ * Returns the number of available segments in the index.
+ *
+ * @param periodDurationUs The duration of the enclosing period in microseconds, or {@link
+ * C#TIME_UNSET} if the period's duration is not yet known.
+ * @param nowUnixTimeUs The current time in milliseconds since the Unix epoch.
+ * @return The number of available segments in the index.
+ */
+ int getAvailableSegmentCount(long periodDurationUs, long nowUnixTimeUs);
+
/**
* 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
+ *
+ *
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
+ *
+ *
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 Whether segments are defined explicitly by the index.
*/
boolean isExplicit();
-
}
diff --git a/library/dash/src/main/java/com/google/android/exoplayer2/source/dash/DashWrappingSegmentIndex.java b/library/dash/src/main/java/com/google/android/exoplayer2/source/dash/DashWrappingSegmentIndex.java
index 3eca7892c4..723fb74739 100644
--- a/library/dash/src/main/java/com/google/android/exoplayer2/source/dash/DashWrappingSegmentIndex.java
+++ b/library/dash/src/main/java/com/google/android/exoplayer2/source/dash/DashWrappingSegmentIndex.java
@@ -41,11 +41,21 @@ public final class DashWrappingSegmentIndex implements DashSegmentIndex {
return 0;
}
+ @Override
+ public long getFirstAvailableSegmentNum(long periodDurationUs, long nowUnixTimeUs) {
+ return 0;
+ }
+
@Override
public int getSegmentCount(long periodDurationUs) {
return chunkIndex.length;
}
+ @Override
+ public int getAvailableSegmentCount(long periodDurationUs, long nowUnixTimeUs) {
+ return chunkIndex.length;
+ }
+
@Override
public long getTimeUs(long segmentNum) {
return chunkIndex.timesUs[(int) segmentNum] - timeOffsetUs;
diff --git a/library/dash/src/main/java/com/google/android/exoplayer2/source/dash/DefaultDashChunkSource.java b/library/dash/src/main/java/com/google/android/exoplayer2/source/dash/DefaultDashChunkSource.java
index 01e51c3f6c..d21d15bea5 100644
--- a/library/dash/src/main/java/com/google/android/exoplayer2/source/dash/DefaultDashChunkSource.java
+++ b/library/dash/src/main/java/com/google/android/exoplayer2/source/dash/DefaultDashChunkSource.java
@@ -15,7 +15,6 @@
*/
package com.google.android.exoplayer2.source.dash;
-import static java.lang.Math.max;
import static java.lang.Math.min;
import android.net.Uri;
@@ -288,9 +287,9 @@ public class DefaultDashChunkSource implements DashChunkSource {
chunkIterators[i] = MediaChunkIterator.EMPTY;
} else {
long firstAvailableSegmentNum =
- representationHolder.getFirstAvailableSegmentNum(manifest, periodIndex, nowUnixTimeUs);
+ representationHolder.getFirstAvailableSegmentNum(nowUnixTimeUs);
long lastAvailableSegmentNum =
- representationHolder.getLastAvailableSegmentNum(manifest, periodIndex, nowUnixTimeUs);
+ representationHolder.getLastAvailableSegmentNum(nowUnixTimeUs);
long segmentNum =
getSegmentNum(
representationHolder,
@@ -342,10 +341,8 @@ public class DefaultDashChunkSource implements DashChunkSource {
return;
}
- long firstAvailableSegmentNum =
- representationHolder.getFirstAvailableSegmentNum(manifest, periodIndex, nowUnixTimeUs);
- long lastAvailableSegmentNum =
- representationHolder.getLastAvailableSegmentNum(manifest, periodIndex, nowUnixTimeUs);
+ long firstAvailableSegmentNum = representationHolder.getFirstAvailableSegmentNum(nowUnixTimeUs);
+ long lastAvailableSegmentNum = representationHolder.getLastAvailableSegmentNum(nowUnixTimeUs);
updateLiveEdgeTimeUs(representationHolder, lastAvailableSegmentNum);
@@ -739,6 +736,11 @@ public class DefaultDashChunkSource implements DashChunkSource {
return segmentIndex.getFirstSegmentNum() + segmentNumShift;
}
+ public long getFirstAvailableSegmentNum(long nowUnixTimeUs) {
+ return segmentIndex.getFirstAvailableSegmentNum(periodDurationUs, nowUnixTimeUs)
+ + segmentNumShift;
+ }
+
public int getSegmentCount() {
return segmentIndex.getSegmentCount(periodDurationUs);
}
@@ -760,35 +762,10 @@ public class DefaultDashChunkSource implements DashChunkSource {
return segmentIndex.getSegmentUrl(segmentNum - segmentNumShift);
}
- public long getFirstAvailableSegmentNum(
- DashManifest manifest, int periodIndex, long nowUnixTimeUs) {
- if (getSegmentCount() == DashSegmentIndex.INDEX_UNBOUNDED
- && manifest.timeShiftBufferDepthMs != C.TIME_UNSET) {
- // The index is itself unbounded. We need to use the current time to calculate the range of
- // available segments.
- long liveEdgeTimeUs = nowUnixTimeUs - C.msToUs(manifest.availabilityStartTimeMs);
- long periodStartUs = C.msToUs(manifest.getPeriod(periodIndex).startMs);
- long liveEdgeTimeInPeriodUs = liveEdgeTimeUs - periodStartUs;
- long bufferDepthUs = C.msToUs(manifest.timeShiftBufferDepthMs);
- return max(getFirstSegmentNum(), getSegmentNum(liveEdgeTimeInPeriodUs - bufferDepthUs));
- }
- return getFirstSegmentNum();
- }
-
- public long getLastAvailableSegmentNum(
- DashManifest manifest, int periodIndex, long nowUnixTimeUs) {
- int availableSegmentCount = getSegmentCount();
- if (availableSegmentCount == DashSegmentIndex.INDEX_UNBOUNDED) {
- // The index is itself unbounded. We need to use the current time to calculate the range of
- // available segments.
- long liveEdgeTimeUs = nowUnixTimeUs - C.msToUs(manifest.availabilityStartTimeMs);
- long periodStartUs = C.msToUs(manifest.getPeriod(periodIndex).startMs);
- long liveEdgeTimeInPeriodUs = liveEdgeTimeUs - periodStartUs;
- // getSegmentNum(liveEdgeTimeInPeriodUs) will not be completed yet, so subtract one to get
- // the index of the last completed segment.
- return getSegmentNum(liveEdgeTimeInPeriodUs) - 1;
- }
- return getFirstSegmentNum() + availableSegmentCount - 1;
+ public long getLastAvailableSegmentNum(long nowUnixTimeUs) {
+ return getFirstAvailableSegmentNum(nowUnixTimeUs)
+ + segmentIndex.getAvailableSegmentCount(periodDurationUs, nowUnixTimeUs)
+ - 1;
}
@Nullable
diff --git a/library/dash/src/main/java/com/google/android/exoplayer2/source/dash/manifest/DashManifestParser.java b/library/dash/src/main/java/com/google/android/exoplayer2/source/dash/manifest/DashManifestParser.java
index ede5df90c4..1a24d82ddf 100644
--- a/library/dash/src/main/java/com/google/android/exoplayer2/source/dash/manifest/DashManifestParser.java
+++ b/library/dash/src/main/java/com/google/android/exoplayer2/source/dash/manifest/DashManifestParser.java
@@ -138,7 +138,13 @@ public class DashManifestParser extends DefaultHandler
location = Uri.parse(xpp.nextText());
} else if (XmlPullParserUtil.isStartTag(xpp, "Period") && !seenEarlyAccessPeriod) {
Pair periodWithDurationMs =
- parsePeriod(xpp, baseUrl, nextPeriodStartMs, baseUrlAvailabilityTimeOffsetUs);
+ parsePeriod(
+ xpp,
+ baseUrl,
+ nextPeriodStartMs,
+ baseUrlAvailabilityTimeOffsetUs,
+ availabilityStartTime,
+ timeShiftBufferDepthMs);
Period period = periodWithDurationMs.first;
if (period.startMs == C.TIME_UNSET) {
if (dynamic) {
@@ -226,10 +232,17 @@ public class DashManifestParser extends DefaultHandler
}
protected Pair parsePeriod(
- XmlPullParser xpp, String baseUrl, long defaultStartMs, long baseUrlAvailabilityTimeOffsetUs)
+ XmlPullParser xpp,
+ String baseUrl,
+ long defaultStartMs,
+ long baseUrlAvailabilityTimeOffsetUs,
+ long availabilityStartTimeMs,
+ long timeShiftBufferDepthMs)
throws XmlPullParserException, IOException {
@Nullable String id = xpp.getAttributeValue(null, "id");
long startMs = parseDuration(xpp, "start", defaultStartMs);
+ long periodStartUnixTimeMs =
+ availabilityStartTimeMs != C.TIME_UNSET ? availabilityStartTimeMs + startMs : C.TIME_UNSET;
long durationMs = parseDuration(xpp, "duration", C.TIME_UNSET);
@Nullable SegmentBase segmentBase = null;
@Nullable Descriptor assetIdentifier = null;
@@ -254,7 +267,9 @@ public class DashManifestParser extends DefaultHandler
segmentBase,
durationMs,
baseUrlAvailabilityTimeOffsetUs,
- segmentBaseAvailabilityTimeOffsetUs));
+ segmentBaseAvailabilityTimeOffsetUs,
+ periodStartUnixTimeMs,
+ timeShiftBufferDepthMs));
} else if (XmlPullParserUtil.isStartTag(xpp, "EventStream")) {
eventStreams.add(parseEventStream(xpp));
} else if (XmlPullParserUtil.isStartTag(xpp, "SegmentBase")) {
@@ -308,7 +323,9 @@ public class DashManifestParser extends DefaultHandler
@Nullable SegmentBase segmentBase,
long periodDurationMs,
long baseUrlAvailabilityTimeOffsetUs,
- long segmentBaseAvailabilityTimeOffsetUs)
+ long segmentBaseAvailabilityTimeOffsetUs,
+ long periodStartUnixTimeMs,
+ long timeShiftBufferDepthMs)
throws XmlPullParserException, IOException {
int id = parseInt(xpp, "id", AdaptationSet.ID_UNSET);
int contentType = parseContentType(xpp);
@@ -428,7 +445,9 @@ public class DashManifestParser extends DefaultHandler
label,
drmSchemeType,
drmSchemeDatas,
- inbandEventStreams));
+ inbandEventStreams,
+ periodStartUnixTimeMs,
+ timeShiftBufferDepthMs));
}
return buildAdaptationSet(
@@ -725,7 +744,9 @@ public class DashManifestParser extends DefaultHandler
@Nullable String label,
@Nullable String extraDrmSchemeType,
ArrayList extraDrmSchemeDatas,
- ArrayList extraInbandEventStreams) {
+ ArrayList extraInbandEventStreams,
+ long periodStartUnixTimeMs,
+ long timeShiftBufferDepthMs) {
Format.Builder formatBuilder = representationInfo.format.buildUpon();
if (label != null) {
formatBuilder.setLabel(label);
@@ -747,7 +768,9 @@ public class DashManifestParser extends DefaultHandler
formatBuilder.build(),
representationInfo.baseUrl,
representationInfo.segmentBase,
- inbandEventStreams);
+ inbandEventStreams,
+ periodStartUnixTimeMs,
+ timeShiftBufferDepthMs);
}
// SegmentBase, SegmentList and SegmentTemplate parsing.
diff --git a/library/dash/src/main/java/com/google/android/exoplayer2/source/dash/manifest/Representation.java b/library/dash/src/main/java/com/google/android/exoplayer2/source/dash/manifest/Representation.java
index 03151631d3..2438835be6 100644
--- a/library/dash/src/main/java/com/google/android/exoplayer2/source/dash/manifest/Representation.java
+++ b/library/dash/src/main/java/com/google/android/exoplayer2/source/dash/manifest/Representation.java
@@ -15,6 +15,8 @@
*/
package com.google.android.exoplayer2.source.dash.manifest;
+import static java.lang.Math.max;
+
import android.net.Uri;
import androidx.annotation.Nullable;
import androidx.annotation.VisibleForTesting;
@@ -71,7 +73,14 @@ public abstract class Representation {
*/
public static Representation newInstance(
long revisionId, Format format, String baseUrl, SegmentBase segmentBase) {
- return newInstance(revisionId, format, baseUrl, segmentBase, /* inbandEventStreams= */ null);
+ return newInstance(
+ revisionId,
+ format,
+ baseUrl,
+ segmentBase,
+ /* inbandEventStreams= */ null,
+ /* periodStartUnixTimeMs= */ C.TIME_UNSET,
+ /* timeShiftBufferDepthMs= */ C.TIME_UNSET);
}
/**
@@ -82,6 +91,9 @@ public abstract class Representation {
* @param baseUrl The base URL.
* @param segmentBase A segment base element for the representation.
* @param inbandEventStreams The in-band event streams in the representation. May be null.
+ * @param periodStartUnixTimeMs The start time of the enclosing {@link Period} in milliseconds
+ * since the Unix epoch, or {@link C#TIME_UNSET} is not applicable.
+ * @param timeShiftBufferDepthMs The {@link DashManifest#timeShiftBufferDepthMs}.
* @return The constructed instance.
*/
public static Representation newInstance(
@@ -89,9 +101,18 @@ public abstract class Representation {
Format format,
String baseUrl,
SegmentBase segmentBase,
- @Nullable List inbandEventStreams) {
+ @Nullable List inbandEventStreams,
+ long periodStartUnixTimeMs,
+ long timeShiftBufferDepthMs) {
return newInstance(
- revisionId, format, baseUrl, segmentBase, inbandEventStreams, /* cacheKey= */ null);
+ revisionId,
+ format,
+ baseUrl,
+ segmentBase,
+ inbandEventStreams,
+ periodStartUnixTimeMs,
+ timeShiftBufferDepthMs,
+ /* cacheKey= */ null);
}
/**
@@ -102,6 +123,9 @@ public abstract class Representation {
* @param baseUrl The base URL of the representation.
* @param segmentBase A segment base element for the representation.
* @param inbandEventStreams The in-band event streams in the representation. May be null.
+ * @param periodStartUnixTimeMs The start time of the enclosing {@link Period} in milliseconds
+ * since the Unix epoch, or {@link C#TIME_UNSET} is not applicable.
+ * @param timeShiftBufferDepthMs The {@link DashManifest#timeShiftBufferDepthMs}.
* @param cacheKey An optional key to be returned from {@link #getCacheKey()}, or null. This
* parameter is ignored if {@code segmentBase} consists of multiple segments.
* @return The constructed instance.
@@ -112,6 +136,8 @@ public abstract class Representation {
String baseUrl,
SegmentBase segmentBase,
@Nullable List inbandEventStreams,
+ long periodStartUnixTimeMs,
+ long timeShiftBufferDepthMs,
@Nullable String cacheKey) {
if (segmentBase instanceof SingleSegmentBase) {
return new SingleSegmentRepresentation(
@@ -124,7 +150,13 @@ public abstract class Representation {
C.LENGTH_UNSET);
} else if (segmentBase instanceof MultiSegmentBase) {
return new MultiSegmentRepresentation(
- revisionId, format, baseUrl, (MultiSegmentBase) segmentBase, inbandEventStreams);
+ revisionId,
+ format,
+ baseUrl,
+ (MultiSegmentBase) segmentBase,
+ inbandEventStreams,
+ periodStartUnixTimeMs,
+ timeShiftBufferDepthMs);
} else {
throw new IllegalArgumentException("segmentBase must be of type SingleSegmentBase or "
+ "MultiSegmentBase");
@@ -277,22 +309,33 @@ public abstract class Representation {
implements DashSegmentIndex {
@VisibleForTesting /* package */ final MultiSegmentBase segmentBase;
+ private final long periodStartUnixTimeUs;
+ private final long timeShiftBufferDepthUs;
/**
+ * Creates the multi-segment Representation.
+ *
* @param revisionId Identifies the revision of the content.
* @param format The format of the representation.
* @param baseUrl The base URL of the representation.
* @param segmentBase The segment base underlying the representation.
* @param inbandEventStreams The in-band event streams in the representation. May be null.
+ * @param periodStartUnixTimeMs The start time of the enclosing {@link Period} in milliseconds
+ * since the Unix epoch, or {@link C#TIME_UNSET} is not applicable.
+ * @param timeShiftBufferDepthMs The {@link DashManifest#timeShiftBufferDepthMs}.
*/
public MultiSegmentRepresentation(
long revisionId,
Format format,
String baseUrl,
MultiSegmentBase segmentBase,
- @Nullable List inbandEventStreams) {
+ @Nullable List inbandEventStreams,
+ long periodStartUnixTimeMs,
+ long timeShiftBufferDepthMs) {
super(revisionId, format, baseUrl, segmentBase, inbandEventStreams);
this.segmentBase = segmentBase;
+ this.periodStartUnixTimeUs = C.msToUs(periodStartUnixTimeMs);
+ this.timeShiftBufferDepthUs = C.msToUs(timeShiftBufferDepthMs);
}
@Override
@@ -339,11 +382,41 @@ public abstract class Representation {
return segmentBase.getFirstSegmentNum();
}
+ @Override
+ public long getFirstAvailableSegmentNum(long periodDurationUs, long nowUnixTimeUs) {
+ long segmentCount = segmentBase.getSegmentCount(periodDurationUs);
+ if (segmentCount != INDEX_UNBOUNDED || timeShiftBufferDepthUs == C.TIME_UNSET) {
+ return segmentBase.getFirstSegmentNum();
+ }
+ // The index is itself unbounded. We need to use the current time to calculate the range of
+ // available segments.
+ long liveEdgeTimeInPeriodUs = nowUnixTimeUs - periodStartUnixTimeUs;
+ long timeShiftBufferStartInPeriodUs = liveEdgeTimeInPeriodUs - timeShiftBufferDepthUs;
+ long timeShiftBufferStartSegmentNum =
+ getSegmentNum(timeShiftBufferStartInPeriodUs, periodDurationUs);
+ return max(getFirstSegmentNum(), timeShiftBufferStartSegmentNum);
+ }
+
@Override
public int getSegmentCount(long periodDurationUs) {
return segmentBase.getSegmentCount(periodDurationUs);
}
+ @Override
+ public int getAvailableSegmentCount(long periodDurationUs, long nowUnixTimeUs) {
+ int segmentCount = segmentBase.getSegmentCount(periodDurationUs);
+ if (segmentCount != INDEX_UNBOUNDED) {
+ return segmentCount;
+ }
+ // The index is itself unbounded. We need to use the current time to calculate the range of
+ // available segments.
+ long liveEdgeTimeInPeriodUs = nowUnixTimeUs - periodStartUnixTimeUs;
+ // getSegmentNum(liveEdgeTimeInPeriodUs) will not be completed yet.
+ long firstIncompleteSegmentNum = getSegmentNum(liveEdgeTimeInPeriodUs, periodDurationUs);
+ long firstAvailableSegmentNum = getFirstAvailableSegmentNum(periodDurationUs, nowUnixTimeUs);
+ return (int) (firstIncompleteSegmentNum - firstAvailableSegmentNum);
+ }
+
@Override
public boolean isExplicit() {
return segmentBase.isExplicit();
diff --git a/library/dash/src/main/java/com/google/android/exoplayer2/source/dash/manifest/SingleSegmentIndex.java b/library/dash/src/main/java/com/google/android/exoplayer2/source/dash/manifest/SingleSegmentIndex.java
index a56a11fe50..7c6c8a7aa9 100644
--- a/library/dash/src/main/java/com/google/android/exoplayer2/source/dash/manifest/SingleSegmentIndex.java
+++ b/library/dash/src/main/java/com/google/android/exoplayer2/source/dash/manifest/SingleSegmentIndex.java
@@ -56,11 +56,21 @@ import com.google.android.exoplayer2.source.dash.DashSegmentIndex;
return 0;
}
+ @Override
+ public long getFirstAvailableSegmentNum(long periodDurationUs, long nowUnixTimeUs) {
+ return 0;
+ }
+
@Override
public int getSegmentCount(long periodDurationUs) {
return 1;
}
+ @Override
+ public int getAvailableSegmentCount(long periodDurationUs, long nowUnixTimeUs) {
+ return 1;
+ }
+
@Override
public boolean isExplicit() {
return true;