From 12e887438b827313aa11f576f4de9f822a0a88eb Mon Sep 17 00:00:00 2001 From: tonihei Date: Tue, 22 Sep 2020 11:38:05 +0100 Subject: [PATCH] Add available segment logic to SegmentIndex This allows to use the same logic from multiple places without duplicating it, encapsulates in its logical place, and allows to change the available segments based on the new availabilityTimeOffset value. The overall effect of this change is a no-op. PiperOrigin-RevId: 333044186 --- .../source/dash/DashSegmentIndex.java | 50 +++++++---- .../source/dash/DashWrappingSegmentIndex.java | 10 +++ .../source/dash/DefaultDashChunkSource.java | 49 +++-------- .../dash/manifest/DashManifestParser.java | 37 +++++++-- .../source/dash/manifest/Representation.java | 83 +++++++++++++++++-- .../dash/manifest/SingleSegmentIndex.java | 10 +++ 6 files changed, 174 insertions(+), 65 deletions(-) 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;