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
This commit is contained in:
tonihei 2020-09-22 11:38:05 +01:00 committed by kim-vde
parent fb4b705cfe
commit 12e887438b
6 changed files with 174 additions and 65 deletions

View File

@ -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}.
* <p>
* 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}.
*
* <p>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.
* <p>
* If true is returned, each segment is defined explicitly by the index data, and all of the
*
* <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
*
* <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 Whether segments are defined explicitly by the index.
*/
boolean isExplicit();
}

View File

@ -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;

View File

@ -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

View File

@ -138,7 +138,13 @@ public class DashManifestParser extends DefaultHandler
location = Uri.parse(xpp.nextText());
} else if (XmlPullParserUtil.isStartTag(xpp, "Period") && !seenEarlyAccessPeriod) {
Pair<Period, Long> 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<Period, Long> 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<SchemeData> extraDrmSchemeDatas,
ArrayList<Descriptor> extraInbandEventStreams) {
ArrayList<Descriptor> 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.

View File

@ -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<Descriptor> inbandEventStreams) {
@Nullable List<Descriptor> 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<Descriptor> 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<Descriptor> inbandEventStreams) {
@Nullable List<Descriptor> 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();

View File

@ -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;