mirror of
https://github.com/samsonjs/media.git
synced 2026-04-27 15:07:40 +00:00
Parse availabilityTimeOffset from DASH manifest.
This value is needed to figure out the last available segment for low-latency live streaming. It may be present in each BaseURL tag and each SegmentList or SegmentTemplate, with the latter one taking precedence. The value is saved as part of MultiSegmentBase where it will be used to retrieve the last available segment index in future changes. PiperOrigin-RevId: 331809871
This commit is contained in:
parent
fa2a77107b
commit
1bc99c2f03
7 changed files with 421 additions and 25 deletions
|
|
@ -40,11 +40,11 @@ import com.google.android.exoplayer2.util.UriUtil;
|
||||||
import com.google.android.exoplayer2.util.Util;
|
import com.google.android.exoplayer2.util.Util;
|
||||||
import com.google.android.exoplayer2.util.XmlPullParserUtil;
|
import com.google.android.exoplayer2.util.XmlPullParserUtil;
|
||||||
import com.google.common.base.Charsets;
|
import com.google.common.base.Charsets;
|
||||||
|
import com.google.common.collect.ImmutableList;
|
||||||
import java.io.ByteArrayOutputStream;
|
import java.io.ByteArrayOutputStream;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.io.InputStream;
|
import java.io.InputStream;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.Collections;
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.UUID;
|
import java.util.UUID;
|
||||||
import java.util.regex.Matcher;
|
import java.util.regex.Matcher;
|
||||||
|
|
@ -115,6 +115,7 @@ public class DashManifestParser extends DefaultHandler
|
||||||
ProgramInformation programInformation = null;
|
ProgramInformation programInformation = null;
|
||||||
UtcTimingElement utcTiming = null;
|
UtcTimingElement utcTiming = null;
|
||||||
Uri location = null;
|
Uri location = null;
|
||||||
|
long baseUrlAvailabilityTimeOffsetUs = dynamic ? 0 : C.TIME_UNSET;
|
||||||
|
|
||||||
List<Period> periods = new ArrayList<>();
|
List<Period> periods = new ArrayList<>();
|
||||||
long nextPeriodStartMs = dynamic ? C.TIME_UNSET : 0;
|
long nextPeriodStartMs = dynamic ? C.TIME_UNSET : 0;
|
||||||
|
|
@ -124,6 +125,8 @@ public class DashManifestParser extends DefaultHandler
|
||||||
xpp.next();
|
xpp.next();
|
||||||
if (XmlPullParserUtil.isStartTag(xpp, "BaseURL")) {
|
if (XmlPullParserUtil.isStartTag(xpp, "BaseURL")) {
|
||||||
if (!seenFirstBaseUrl) {
|
if (!seenFirstBaseUrl) {
|
||||||
|
baseUrlAvailabilityTimeOffsetUs =
|
||||||
|
parseAvailabilityTimeOffsetUs(xpp, baseUrlAvailabilityTimeOffsetUs);
|
||||||
baseUrl = parseBaseUrl(xpp, baseUrl);
|
baseUrl = parseBaseUrl(xpp, baseUrl);
|
||||||
seenFirstBaseUrl = true;
|
seenFirstBaseUrl = true;
|
||||||
}
|
}
|
||||||
|
|
@ -134,7 +137,8 @@ public class DashManifestParser extends DefaultHandler
|
||||||
} else if (XmlPullParserUtil.isStartTag(xpp, "Location")) {
|
} else if (XmlPullParserUtil.isStartTag(xpp, "Location")) {
|
||||||
location = Uri.parse(xpp.nextText());
|
location = Uri.parse(xpp.nextText());
|
||||||
} else if (XmlPullParserUtil.isStartTag(xpp, "Period") && !seenEarlyAccessPeriod) {
|
} else if (XmlPullParserUtil.isStartTag(xpp, "Period") && !seenEarlyAccessPeriod) {
|
||||||
Pair<Period, Long> periodWithDurationMs = parsePeriod(xpp, baseUrl, nextPeriodStartMs);
|
Pair<Period, Long> periodWithDurationMs =
|
||||||
|
parsePeriod(xpp, baseUrl, nextPeriodStartMs, baseUrlAvailabilityTimeOffsetUs);
|
||||||
Period period = periodWithDurationMs.first;
|
Period period = periodWithDurationMs.first;
|
||||||
if (period.startMs == C.TIME_UNSET) {
|
if (period.startMs == C.TIME_UNSET) {
|
||||||
if (dynamic) {
|
if (dynamic) {
|
||||||
|
|
@ -221,7 +225,8 @@ public class DashManifestParser extends DefaultHandler
|
||||||
return new UtcTimingElement(schemeIdUri, value);
|
return new UtcTimingElement(schemeIdUri, value);
|
||||||
}
|
}
|
||||||
|
|
||||||
protected Pair<Period, Long> parsePeriod(XmlPullParser xpp, String baseUrl, long defaultStartMs)
|
protected Pair<Period, Long> parsePeriod(
|
||||||
|
XmlPullParser xpp, String baseUrl, long defaultStartMs, long baseUrlAvailabilityTimeOffsetUs)
|
||||||
throws XmlPullParserException, IOException {
|
throws XmlPullParserException, IOException {
|
||||||
@Nullable String id = xpp.getAttributeValue(null, "id");
|
@Nullable String id = xpp.getAttributeValue(null, "id");
|
||||||
long startMs = parseDuration(xpp, "start", defaultStartMs);
|
long startMs = parseDuration(xpp, "start", defaultStartMs);
|
||||||
|
|
@ -231,23 +236,50 @@ public class DashManifestParser extends DefaultHandler
|
||||||
List<AdaptationSet> adaptationSets = new ArrayList<>();
|
List<AdaptationSet> adaptationSets = new ArrayList<>();
|
||||||
List<EventStream> eventStreams = new ArrayList<>();
|
List<EventStream> eventStreams = new ArrayList<>();
|
||||||
boolean seenFirstBaseUrl = false;
|
boolean seenFirstBaseUrl = false;
|
||||||
|
long segmentBaseAvailabilityTimeOffsetUs = C.TIME_UNSET;
|
||||||
do {
|
do {
|
||||||
xpp.next();
|
xpp.next();
|
||||||
if (XmlPullParserUtil.isStartTag(xpp, "BaseURL")) {
|
if (XmlPullParserUtil.isStartTag(xpp, "BaseURL")) {
|
||||||
if (!seenFirstBaseUrl) {
|
if (!seenFirstBaseUrl) {
|
||||||
|
baseUrlAvailabilityTimeOffsetUs =
|
||||||
|
parseAvailabilityTimeOffsetUs(xpp, baseUrlAvailabilityTimeOffsetUs);
|
||||||
baseUrl = parseBaseUrl(xpp, baseUrl);
|
baseUrl = parseBaseUrl(xpp, baseUrl);
|
||||||
seenFirstBaseUrl = true;
|
seenFirstBaseUrl = true;
|
||||||
}
|
}
|
||||||
} else if (XmlPullParserUtil.isStartTag(xpp, "AdaptationSet")) {
|
} else if (XmlPullParserUtil.isStartTag(xpp, "AdaptationSet")) {
|
||||||
adaptationSets.add(parseAdaptationSet(xpp, baseUrl, segmentBase, durationMs));
|
adaptationSets.add(
|
||||||
|
parseAdaptationSet(
|
||||||
|
xpp,
|
||||||
|
baseUrl,
|
||||||
|
segmentBase,
|
||||||
|
durationMs,
|
||||||
|
baseUrlAvailabilityTimeOffsetUs,
|
||||||
|
segmentBaseAvailabilityTimeOffsetUs));
|
||||||
} else if (XmlPullParserUtil.isStartTag(xpp, "EventStream")) {
|
} else if (XmlPullParserUtil.isStartTag(xpp, "EventStream")) {
|
||||||
eventStreams.add(parseEventStream(xpp));
|
eventStreams.add(parseEventStream(xpp));
|
||||||
} else if (XmlPullParserUtil.isStartTag(xpp, "SegmentBase")) {
|
} else if (XmlPullParserUtil.isStartTag(xpp, "SegmentBase")) {
|
||||||
segmentBase = parseSegmentBase(xpp, null);
|
segmentBase = parseSegmentBase(xpp, /* parent= */ null);
|
||||||
} else if (XmlPullParserUtil.isStartTag(xpp, "SegmentList")) {
|
} else if (XmlPullParserUtil.isStartTag(xpp, "SegmentList")) {
|
||||||
segmentBase = parseSegmentList(xpp, null, durationMs);
|
segmentBaseAvailabilityTimeOffsetUs =
|
||||||
|
parseAvailabilityTimeOffsetUs(xpp, /* parentAvailabilityTimeOffsetUs= */ C.TIME_UNSET);
|
||||||
|
segmentBase =
|
||||||
|
parseSegmentList(
|
||||||
|
xpp,
|
||||||
|
/* parent= */ null,
|
||||||
|
durationMs,
|
||||||
|
baseUrlAvailabilityTimeOffsetUs,
|
||||||
|
segmentBaseAvailabilityTimeOffsetUs);
|
||||||
} else if (XmlPullParserUtil.isStartTag(xpp, "SegmentTemplate")) {
|
} else if (XmlPullParserUtil.isStartTag(xpp, "SegmentTemplate")) {
|
||||||
segmentBase = parseSegmentTemplate(xpp, null, Collections.emptyList(), durationMs);
|
segmentBaseAvailabilityTimeOffsetUs =
|
||||||
|
parseAvailabilityTimeOffsetUs(xpp, /* parentAvailabilityTimeOffsetUs= */ C.TIME_UNSET);
|
||||||
|
segmentBase =
|
||||||
|
parseSegmentTemplate(
|
||||||
|
xpp,
|
||||||
|
/* parent= */ null,
|
||||||
|
ImmutableList.of(),
|
||||||
|
durationMs,
|
||||||
|
baseUrlAvailabilityTimeOffsetUs,
|
||||||
|
segmentBaseAvailabilityTimeOffsetUs);
|
||||||
} else if (XmlPullParserUtil.isStartTag(xpp, "AssetIdentifier")) {
|
} else if (XmlPullParserUtil.isStartTag(xpp, "AssetIdentifier")) {
|
||||||
assetIdentifier = parseDescriptor(xpp, "AssetIdentifier");
|
assetIdentifier = parseDescriptor(xpp, "AssetIdentifier");
|
||||||
} else {
|
} else {
|
||||||
|
|
@ -271,7 +303,12 @@ public class DashManifestParser extends DefaultHandler
|
||||||
// AdaptationSet parsing.
|
// AdaptationSet parsing.
|
||||||
|
|
||||||
protected AdaptationSet parseAdaptationSet(
|
protected AdaptationSet parseAdaptationSet(
|
||||||
XmlPullParser xpp, String baseUrl, @Nullable SegmentBase segmentBase, long periodDurationMs)
|
XmlPullParser xpp,
|
||||||
|
String baseUrl,
|
||||||
|
@Nullable SegmentBase segmentBase,
|
||||||
|
long periodDurationMs,
|
||||||
|
long baseUrlAvailabilityTimeOffsetUs,
|
||||||
|
long segmentBaseAvailabilityTimeOffsetUs)
|
||||||
throws XmlPullParserException, IOException {
|
throws XmlPullParserException, IOException {
|
||||||
int id = parseInt(xpp, "id", AdaptationSet.ID_UNSET);
|
int id = parseInt(xpp, "id", AdaptationSet.ID_UNSET);
|
||||||
int contentType = parseContentType(xpp);
|
int contentType = parseContentType(xpp);
|
||||||
|
|
@ -299,6 +336,8 @@ public class DashManifestParser extends DefaultHandler
|
||||||
xpp.next();
|
xpp.next();
|
||||||
if (XmlPullParserUtil.isStartTag(xpp, "BaseURL")) {
|
if (XmlPullParserUtil.isStartTag(xpp, "BaseURL")) {
|
||||||
if (!seenFirstBaseUrl) {
|
if (!seenFirstBaseUrl) {
|
||||||
|
baseUrlAvailabilityTimeOffsetUs =
|
||||||
|
parseAvailabilityTimeOffsetUs(xpp, baseUrlAvailabilityTimeOffsetUs);
|
||||||
baseUrl = parseBaseUrl(xpp, baseUrl);
|
baseUrl = parseBaseUrl(xpp, baseUrl);
|
||||||
seenFirstBaseUrl = true;
|
seenFirstBaseUrl = true;
|
||||||
}
|
}
|
||||||
|
|
@ -341,7 +380,9 @@ public class DashManifestParser extends DefaultHandler
|
||||||
essentialProperties,
|
essentialProperties,
|
||||||
supplementalProperties,
|
supplementalProperties,
|
||||||
segmentBase,
|
segmentBase,
|
||||||
periodDurationMs);
|
periodDurationMs,
|
||||||
|
baseUrlAvailabilityTimeOffsetUs,
|
||||||
|
segmentBaseAvailabilityTimeOffsetUs);
|
||||||
contentType =
|
contentType =
|
||||||
checkContentTypeConsistency(
|
checkContentTypeConsistency(
|
||||||
contentType, MimeTypes.getTrackType(representationInfo.format.sampleMimeType));
|
contentType, MimeTypes.getTrackType(representationInfo.format.sampleMimeType));
|
||||||
|
|
@ -349,11 +390,26 @@ public class DashManifestParser extends DefaultHandler
|
||||||
} else if (XmlPullParserUtil.isStartTag(xpp, "SegmentBase")) {
|
} else if (XmlPullParserUtil.isStartTag(xpp, "SegmentBase")) {
|
||||||
segmentBase = parseSegmentBase(xpp, (SingleSegmentBase) segmentBase);
|
segmentBase = parseSegmentBase(xpp, (SingleSegmentBase) segmentBase);
|
||||||
} else if (XmlPullParserUtil.isStartTag(xpp, "SegmentList")) {
|
} else if (XmlPullParserUtil.isStartTag(xpp, "SegmentList")) {
|
||||||
segmentBase = parseSegmentList(xpp, (SegmentList) segmentBase, periodDurationMs);
|
segmentBaseAvailabilityTimeOffsetUs =
|
||||||
|
parseAvailabilityTimeOffsetUs(xpp, segmentBaseAvailabilityTimeOffsetUs);
|
||||||
|
segmentBase =
|
||||||
|
parseSegmentList(
|
||||||
|
xpp,
|
||||||
|
(SegmentList) segmentBase,
|
||||||
|
periodDurationMs,
|
||||||
|
baseUrlAvailabilityTimeOffsetUs,
|
||||||
|
segmentBaseAvailabilityTimeOffsetUs);
|
||||||
} else if (XmlPullParserUtil.isStartTag(xpp, "SegmentTemplate")) {
|
} else if (XmlPullParserUtil.isStartTag(xpp, "SegmentTemplate")) {
|
||||||
|
segmentBaseAvailabilityTimeOffsetUs =
|
||||||
|
parseAvailabilityTimeOffsetUs(xpp, segmentBaseAvailabilityTimeOffsetUs);
|
||||||
segmentBase =
|
segmentBase =
|
||||||
parseSegmentTemplate(
|
parseSegmentTemplate(
|
||||||
xpp, (SegmentTemplate) segmentBase, supplementalProperties, periodDurationMs);
|
xpp,
|
||||||
|
(SegmentTemplate) segmentBase,
|
||||||
|
supplementalProperties,
|
||||||
|
periodDurationMs,
|
||||||
|
baseUrlAvailabilityTimeOffsetUs,
|
||||||
|
segmentBaseAvailabilityTimeOffsetUs);
|
||||||
} else if (XmlPullParserUtil.isStartTag(xpp, "InbandEventStream")) {
|
} else if (XmlPullParserUtil.isStartTag(xpp, "InbandEventStream")) {
|
||||||
inbandEventStreams.add(parseDescriptor(xpp, "InbandEventStream"));
|
inbandEventStreams.add(parseDescriptor(xpp, "InbandEventStream"));
|
||||||
} else if (XmlPullParserUtil.isStartTag(xpp, "Label")) {
|
} else if (XmlPullParserUtil.isStartTag(xpp, "Label")) {
|
||||||
|
|
@ -514,7 +570,9 @@ public class DashManifestParser extends DefaultHandler
|
||||||
List<Descriptor> adaptationSetEssentialProperties,
|
List<Descriptor> adaptationSetEssentialProperties,
|
||||||
List<Descriptor> adaptationSetSupplementalProperties,
|
List<Descriptor> adaptationSetSupplementalProperties,
|
||||||
@Nullable SegmentBase segmentBase,
|
@Nullable SegmentBase segmentBase,
|
||||||
long periodDurationMs)
|
long periodDurationMs,
|
||||||
|
long baseUrlAvailabilityTimeOffsetUs,
|
||||||
|
long segmentBaseAvailabilityTimeOffsetUs)
|
||||||
throws XmlPullParserException, IOException {
|
throws XmlPullParserException, IOException {
|
||||||
String id = xpp.getAttributeValue(null, "id");
|
String id = xpp.getAttributeValue(null, "id");
|
||||||
int bandwidth = parseInt(xpp, "bandwidth", Format.NO_VALUE);
|
int bandwidth = parseInt(xpp, "bandwidth", Format.NO_VALUE);
|
||||||
|
|
@ -538,6 +596,8 @@ public class DashManifestParser extends DefaultHandler
|
||||||
xpp.next();
|
xpp.next();
|
||||||
if (XmlPullParserUtil.isStartTag(xpp, "BaseURL")) {
|
if (XmlPullParserUtil.isStartTag(xpp, "BaseURL")) {
|
||||||
if (!seenFirstBaseUrl) {
|
if (!seenFirstBaseUrl) {
|
||||||
|
baseUrlAvailabilityTimeOffsetUs =
|
||||||
|
parseAvailabilityTimeOffsetUs(xpp, baseUrlAvailabilityTimeOffsetUs);
|
||||||
baseUrl = parseBaseUrl(xpp, baseUrl);
|
baseUrl = parseBaseUrl(xpp, baseUrl);
|
||||||
seenFirstBaseUrl = true;
|
seenFirstBaseUrl = true;
|
||||||
}
|
}
|
||||||
|
|
@ -546,14 +606,26 @@ public class DashManifestParser extends DefaultHandler
|
||||||
} else if (XmlPullParserUtil.isStartTag(xpp, "SegmentBase")) {
|
} else if (XmlPullParserUtil.isStartTag(xpp, "SegmentBase")) {
|
||||||
segmentBase = parseSegmentBase(xpp, (SingleSegmentBase) segmentBase);
|
segmentBase = parseSegmentBase(xpp, (SingleSegmentBase) segmentBase);
|
||||||
} else if (XmlPullParserUtil.isStartTag(xpp, "SegmentList")) {
|
} else if (XmlPullParserUtil.isStartTag(xpp, "SegmentList")) {
|
||||||
segmentBase = parseSegmentList(xpp, (SegmentList) segmentBase, periodDurationMs);
|
segmentBaseAvailabilityTimeOffsetUs =
|
||||||
|
parseAvailabilityTimeOffsetUs(xpp, segmentBaseAvailabilityTimeOffsetUs);
|
||||||
|
segmentBase =
|
||||||
|
parseSegmentList(
|
||||||
|
xpp,
|
||||||
|
(SegmentList) segmentBase,
|
||||||
|
periodDurationMs,
|
||||||
|
baseUrlAvailabilityTimeOffsetUs,
|
||||||
|
segmentBaseAvailabilityTimeOffsetUs);
|
||||||
} else if (XmlPullParserUtil.isStartTag(xpp, "SegmentTemplate")) {
|
} else if (XmlPullParserUtil.isStartTag(xpp, "SegmentTemplate")) {
|
||||||
|
segmentBaseAvailabilityTimeOffsetUs =
|
||||||
|
parseAvailabilityTimeOffsetUs(xpp, segmentBaseAvailabilityTimeOffsetUs);
|
||||||
segmentBase =
|
segmentBase =
|
||||||
parseSegmentTemplate(
|
parseSegmentTemplate(
|
||||||
xpp,
|
xpp,
|
||||||
(SegmentTemplate) segmentBase,
|
(SegmentTemplate) segmentBase,
|
||||||
adaptationSetSupplementalProperties,
|
adaptationSetSupplementalProperties,
|
||||||
periodDurationMs);
|
periodDurationMs,
|
||||||
|
baseUrlAvailabilityTimeOffsetUs,
|
||||||
|
segmentBaseAvailabilityTimeOffsetUs);
|
||||||
} else if (XmlPullParserUtil.isStartTag(xpp, "ContentProtection")) {
|
} else if (XmlPullParserUtil.isStartTag(xpp, "ContentProtection")) {
|
||||||
Pair<String, SchemeData> contentProtection = parseContentProtection(xpp);
|
Pair<String, SchemeData> contentProtection = parseContentProtection(xpp);
|
||||||
if (contentProtection.first != null) {
|
if (contentProtection.first != null) {
|
||||||
|
|
@ -718,7 +790,11 @@ public class DashManifestParser extends DefaultHandler
|
||||||
}
|
}
|
||||||
|
|
||||||
protected SegmentList parseSegmentList(
|
protected SegmentList parseSegmentList(
|
||||||
XmlPullParser xpp, @Nullable SegmentList parent, long periodDurationMs)
|
XmlPullParser xpp,
|
||||||
|
@Nullable SegmentList parent,
|
||||||
|
long periodDurationMs,
|
||||||
|
long baseUrlAvailabilityTimeOffsetUs,
|
||||||
|
long segmentBaseAvailabilityTimeOffsetUs)
|
||||||
throws XmlPullParserException, IOException {
|
throws XmlPullParserException, IOException {
|
||||||
|
|
||||||
long timescale = parseLong(xpp, "timescale", parent != null ? parent.timescale : 1);
|
long timescale = parseLong(xpp, "timescale", parent != null ? parent.timescale : 1);
|
||||||
|
|
@ -726,6 +802,9 @@ public class DashManifestParser extends DefaultHandler
|
||||||
parent != null ? parent.presentationTimeOffset : 0);
|
parent != null ? parent.presentationTimeOffset : 0);
|
||||||
long duration = parseLong(xpp, "duration", parent != null ? parent.duration : C.TIME_UNSET);
|
long duration = parseLong(xpp, "duration", parent != null ? parent.duration : C.TIME_UNSET);
|
||||||
long startNumber = parseLong(xpp, "startNumber", parent != null ? parent.startNumber : 1);
|
long startNumber = parseLong(xpp, "startNumber", parent != null ? parent.startNumber : 1);
|
||||||
|
long availabilityTimeOffsetUs =
|
||||||
|
getFinalAvailabilityTimeOffset(
|
||||||
|
baseUrlAvailabilityTimeOffsetUs, segmentBaseAvailabilityTimeOffsetUs);
|
||||||
|
|
||||||
RangedUri initialization = null;
|
RangedUri initialization = null;
|
||||||
List<SegmentTimelineElement> timeline = null;
|
List<SegmentTimelineElement> timeline = null;
|
||||||
|
|
@ -753,8 +832,15 @@ public class DashManifestParser extends DefaultHandler
|
||||||
segments = segments != null ? segments : parent.mediaSegments;
|
segments = segments != null ? segments : parent.mediaSegments;
|
||||||
}
|
}
|
||||||
|
|
||||||
return buildSegmentList(initialization, timescale, presentationTimeOffset,
|
return buildSegmentList(
|
||||||
startNumber, duration, timeline, segments);
|
initialization,
|
||||||
|
timescale,
|
||||||
|
presentationTimeOffset,
|
||||||
|
startNumber,
|
||||||
|
duration,
|
||||||
|
timeline,
|
||||||
|
availabilityTimeOffsetUs,
|
||||||
|
segments);
|
||||||
}
|
}
|
||||||
|
|
||||||
protected SegmentList buildSegmentList(
|
protected SegmentList buildSegmentList(
|
||||||
|
|
@ -764,16 +850,26 @@ public class DashManifestParser extends DefaultHandler
|
||||||
long startNumber,
|
long startNumber,
|
||||||
long duration,
|
long duration,
|
||||||
@Nullable List<SegmentTimelineElement> timeline,
|
@Nullable List<SegmentTimelineElement> timeline,
|
||||||
|
long availabilityTimeOffsetUs,
|
||||||
@Nullable List<RangedUri> segments) {
|
@Nullable List<RangedUri> segments) {
|
||||||
return new SegmentList(initialization, timescale, presentationTimeOffset,
|
return new SegmentList(
|
||||||
startNumber, duration, timeline, segments);
|
initialization,
|
||||||
|
timescale,
|
||||||
|
presentationTimeOffset,
|
||||||
|
startNumber,
|
||||||
|
duration,
|
||||||
|
timeline,
|
||||||
|
availabilityTimeOffsetUs,
|
||||||
|
segments);
|
||||||
}
|
}
|
||||||
|
|
||||||
protected SegmentTemplate parseSegmentTemplate(
|
protected SegmentTemplate parseSegmentTemplate(
|
||||||
XmlPullParser xpp,
|
XmlPullParser xpp,
|
||||||
@Nullable SegmentTemplate parent,
|
@Nullable SegmentTemplate parent,
|
||||||
List<Descriptor> adaptationSetSupplementalProperties,
|
List<Descriptor> adaptationSetSupplementalProperties,
|
||||||
long periodDurationMs)
|
long periodDurationMs,
|
||||||
|
long baseUrlAvailabilityTimeOffsetUs,
|
||||||
|
long segmentBaseAvailabilityTimeOffsetUs)
|
||||||
throws XmlPullParserException, IOException {
|
throws XmlPullParserException, IOException {
|
||||||
long timescale = parseLong(xpp, "timescale", parent != null ? parent.timescale : 1);
|
long timescale = parseLong(xpp, "timescale", parent != null ? parent.timescale : 1);
|
||||||
long presentationTimeOffset = parseLong(xpp, "presentationTimeOffset",
|
long presentationTimeOffset = parseLong(xpp, "presentationTimeOffset",
|
||||||
|
|
@ -782,6 +878,9 @@ public class DashManifestParser extends DefaultHandler
|
||||||
long startNumber = parseLong(xpp, "startNumber", parent != null ? parent.startNumber : 1);
|
long startNumber = parseLong(xpp, "startNumber", parent != null ? parent.startNumber : 1);
|
||||||
long endNumber =
|
long endNumber =
|
||||||
parseLastSegmentNumberSupplementalProperty(adaptationSetSupplementalProperties);
|
parseLastSegmentNumberSupplementalProperty(adaptationSetSupplementalProperties);
|
||||||
|
long availabilityTimeOffsetUs =
|
||||||
|
getFinalAvailabilityTimeOffset(
|
||||||
|
baseUrlAvailabilityTimeOffsetUs, segmentBaseAvailabilityTimeOffsetUs);
|
||||||
|
|
||||||
UrlTemplate mediaTemplate = parseUrlTemplate(xpp, "media",
|
UrlTemplate mediaTemplate = parseUrlTemplate(xpp, "media",
|
||||||
parent != null ? parent.mediaTemplate : null);
|
parent != null ? parent.mediaTemplate : null);
|
||||||
|
|
@ -815,6 +914,7 @@ public class DashManifestParser extends DefaultHandler
|
||||||
endNumber,
|
endNumber,
|
||||||
duration,
|
duration,
|
||||||
timeline,
|
timeline,
|
||||||
|
availabilityTimeOffsetUs,
|
||||||
initializationTemplate,
|
initializationTemplate,
|
||||||
mediaTemplate);
|
mediaTemplate);
|
||||||
}
|
}
|
||||||
|
|
@ -827,6 +927,7 @@ public class DashManifestParser extends DefaultHandler
|
||||||
long endNumber,
|
long endNumber,
|
||||||
long duration,
|
long duration,
|
||||||
List<SegmentTimelineElement> timeline,
|
List<SegmentTimelineElement> timeline,
|
||||||
|
long availabilityTimeOffsetUs,
|
||||||
@Nullable UrlTemplate initializationTemplate,
|
@Nullable UrlTemplate initializationTemplate,
|
||||||
@Nullable UrlTemplate mediaTemplate) {
|
@Nullable UrlTemplate mediaTemplate) {
|
||||||
return new SegmentTemplate(
|
return new SegmentTemplate(
|
||||||
|
|
@ -837,6 +938,7 @@ public class DashManifestParser extends DefaultHandler
|
||||||
endNumber,
|
endNumber,
|
||||||
duration,
|
duration,
|
||||||
timeline,
|
timeline,
|
||||||
|
availabilityTimeOffsetUs,
|
||||||
initializationTemplate,
|
initializationTemplate,
|
||||||
mediaTemplate);
|
mediaTemplate);
|
||||||
}
|
}
|
||||||
|
|
@ -1151,6 +1253,27 @@ public class DashManifestParser extends DefaultHandler
|
||||||
return UriUtil.resolve(parentBaseUrl, parseText(xpp, "BaseURL"));
|
return UriUtil.resolve(parentBaseUrl, parseText(xpp, "BaseURL"));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Parses the availabilityTimeOffset value and returns the parsed value or the parent value if it
|
||||||
|
* doesn't exist.
|
||||||
|
*
|
||||||
|
* @param xpp The parser from which to read.
|
||||||
|
* @param parentAvailabilityTimeOffsetUs The availability time offset of a parent element in
|
||||||
|
* microseconds.
|
||||||
|
* @return The parsed availabilityTimeOffset in microseconds.
|
||||||
|
*/
|
||||||
|
protected long parseAvailabilityTimeOffsetUs(
|
||||||
|
XmlPullParser xpp, long parentAvailabilityTimeOffsetUs) {
|
||||||
|
String value = xpp.getAttributeValue(/* namespace= */ null, "availabilityTimeOffset");
|
||||||
|
if (value == null) {
|
||||||
|
return parentAvailabilityTimeOffsetUs;
|
||||||
|
}
|
||||||
|
if ("INF".equals(value)) {
|
||||||
|
return Long.MAX_VALUE;
|
||||||
|
}
|
||||||
|
return (long) (Float.parseFloat(value) * C.MICROS_PER_SECOND);
|
||||||
|
}
|
||||||
|
|
||||||
// AudioChannelConfiguration parsing.
|
// AudioChannelConfiguration parsing.
|
||||||
|
|
||||||
protected int parseAudioChannelConfiguration(XmlPullParser xpp)
|
protected int parseAudioChannelConfiguration(XmlPullParser xpp)
|
||||||
|
|
@ -1569,6 +1692,20 @@ public class DashManifestParser extends DefaultHandler
|
||||||
return C.INDEX_UNSET;
|
return C.INDEX_UNSET;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static long getFinalAvailabilityTimeOffset(
|
||||||
|
long baseUrlAvailabilityTimeOffsetUs, long segmentBaseAvailabilityTimeOffsetUs) {
|
||||||
|
long availabilityTimeOffsetUs = segmentBaseAvailabilityTimeOffsetUs;
|
||||||
|
if (availabilityTimeOffsetUs == C.TIME_UNSET) {
|
||||||
|
// Fall back to BaseURL values if no SegmentBase specifies an offset.
|
||||||
|
availabilityTimeOffsetUs = baseUrlAvailabilityTimeOffsetUs;
|
||||||
|
}
|
||||||
|
if (availabilityTimeOffsetUs == Long.MAX_VALUE) {
|
||||||
|
// Replace INF value with TIME_UNSET to specify that all segments are available immediately.
|
||||||
|
availabilityTimeOffsetUs = C.TIME_UNSET;
|
||||||
|
}
|
||||||
|
return availabilityTimeOffsetUs;
|
||||||
|
}
|
||||||
|
|
||||||
/** A parsed Representation element. */
|
/** A parsed Representation element. */
|
||||||
protected static final class RepresentationInfo {
|
protected static final class RepresentationInfo {
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -17,6 +17,7 @@ package com.google.android.exoplayer2.source.dash.manifest;
|
||||||
|
|
||||||
import android.net.Uri;
|
import android.net.Uri;
|
||||||
import androidx.annotation.Nullable;
|
import androidx.annotation.Nullable;
|
||||||
|
import androidx.annotation.VisibleForTesting;
|
||||||
import com.google.android.exoplayer2.C;
|
import com.google.android.exoplayer2.C;
|
||||||
import com.google.android.exoplayer2.Format;
|
import com.google.android.exoplayer2.Format;
|
||||||
import com.google.android.exoplayer2.source.dash.DashSegmentIndex;
|
import com.google.android.exoplayer2.source.dash.DashSegmentIndex;
|
||||||
|
|
@ -275,7 +276,7 @@ public abstract class Representation {
|
||||||
public static class MultiSegmentRepresentation extends Representation
|
public static class MultiSegmentRepresentation extends Representation
|
||||||
implements DashSegmentIndex {
|
implements DashSegmentIndex {
|
||||||
|
|
||||||
private final MultiSegmentBase segmentBase;
|
@VisibleForTesting /* package */ final MultiSegmentBase segmentBase;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param revisionId Identifies the revision of the content.
|
* @param revisionId Identifies the revision of the content.
|
||||||
|
|
|
||||||
|
|
@ -120,6 +120,15 @@ public abstract class SegmentBase {
|
||||||
/* package */ final long duration;
|
/* package */ final long duration;
|
||||||
@Nullable /* package */ final List<SegmentTimelineElement> segmentTimeline;
|
@Nullable /* package */ final List<SegmentTimelineElement> segmentTimeline;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Offset to the current realtime at which segments become available, in microseconds, or {@link
|
||||||
|
* C#TIME_UNSET} if all segments are available immediately.
|
||||||
|
*
|
||||||
|
* <p>Segments will be available once their end time ≤ currentRealTime +
|
||||||
|
* availabilityTimeOffset.
|
||||||
|
*/
|
||||||
|
/* package */ final long availabilityTimeOffsetUs;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param initialization A {@link RangedUri} corresponding to initialization data, if such data
|
* @param initialization A {@link RangedUri} corresponding to initialization data, if such data
|
||||||
* exists.
|
* exists.
|
||||||
|
|
@ -133,6 +142,8 @@ public abstract class SegmentBase {
|
||||||
* @param segmentTimeline A segment timeline corresponding to the segments. If null, then
|
* @param segmentTimeline A segment timeline corresponding to the segments. If null, then
|
||||||
* segments are assumed to be of fixed duration as specified by the {@code duration}
|
* segments are assumed to be of fixed duration as specified by the {@code duration}
|
||||||
* parameter.
|
* parameter.
|
||||||
|
* @param availabilityTimeOffsetUs The offset to the current realtime at which segments become
|
||||||
|
* available in microseconds, or {@link C#TIME_UNSET} if not applicable.
|
||||||
*/
|
*/
|
||||||
public MultiSegmentBase(
|
public MultiSegmentBase(
|
||||||
@Nullable RangedUri initialization,
|
@Nullable RangedUri initialization,
|
||||||
|
|
@ -140,11 +151,13 @@ public abstract class SegmentBase {
|
||||||
long presentationTimeOffset,
|
long presentationTimeOffset,
|
||||||
long startNumber,
|
long startNumber,
|
||||||
long duration,
|
long duration,
|
||||||
@Nullable List<SegmentTimelineElement> segmentTimeline) {
|
@Nullable List<SegmentTimelineElement> segmentTimeline,
|
||||||
|
long availabilityTimeOffsetUs) {
|
||||||
super(initialization, timescale, presentationTimeOffset);
|
super(initialization, timescale, presentationTimeOffset);
|
||||||
this.startNumber = startNumber;
|
this.startNumber = startNumber;
|
||||||
this.duration = duration;
|
this.duration = duration;
|
||||||
this.segmentTimeline = segmentTimeline;
|
this.segmentTimeline = segmentTimeline;
|
||||||
|
this.availabilityTimeOffsetUs = availabilityTimeOffsetUs;
|
||||||
}
|
}
|
||||||
|
|
||||||
/** @see DashSegmentIndex#getSegmentNum(long, long) */
|
/** @see DashSegmentIndex#getSegmentNum(long, long) */
|
||||||
|
|
@ -255,6 +268,8 @@ public abstract class SegmentBase {
|
||||||
* @param segmentTimeline A segment timeline corresponding to the segments. If null, then
|
* @param segmentTimeline A segment timeline corresponding to the segments. If null, then
|
||||||
* segments are assumed to be of fixed duration as specified by the {@code duration}
|
* segments are assumed to be of fixed duration as specified by the {@code duration}
|
||||||
* parameter.
|
* parameter.
|
||||||
|
* @param availabilityTimeOffsetUs The offset to the current realtime at which segments become
|
||||||
|
* available in microseconds, or {@link C#TIME_UNSET} if not applicable.
|
||||||
* @param mediaSegments A list of {@link RangedUri}s indicating the locations of the segments.
|
* @param mediaSegments A list of {@link RangedUri}s indicating the locations of the segments.
|
||||||
*/
|
*/
|
||||||
public SegmentList(
|
public SegmentList(
|
||||||
|
|
@ -264,9 +279,16 @@ public abstract class SegmentBase {
|
||||||
long startNumber,
|
long startNumber,
|
||||||
long duration,
|
long duration,
|
||||||
@Nullable List<SegmentTimelineElement> segmentTimeline,
|
@Nullable List<SegmentTimelineElement> segmentTimeline,
|
||||||
|
long availabilityTimeOffsetUs,
|
||||||
@Nullable List<RangedUri> mediaSegments) {
|
@Nullable List<RangedUri> mediaSegments) {
|
||||||
super(initialization, timescale, presentationTimeOffset, startNumber, duration,
|
super(
|
||||||
segmentTimeline);
|
initialization,
|
||||||
|
timescale,
|
||||||
|
presentationTimeOffset,
|
||||||
|
startNumber,
|
||||||
|
duration,
|
||||||
|
segmentTimeline,
|
||||||
|
availabilityTimeOffsetUs);
|
||||||
this.mediaSegments = mediaSegments;
|
this.mediaSegments = mediaSegments;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -311,6 +333,8 @@ public abstract class SegmentBase {
|
||||||
* @param segmentTimeline A segment timeline corresponding to the segments. If null, then
|
* @param segmentTimeline A segment timeline corresponding to the segments. If null, then
|
||||||
* segments are assumed to be of fixed duration as specified by the {@code duration}
|
* segments are assumed to be of fixed duration as specified by the {@code duration}
|
||||||
* parameter.
|
* parameter.
|
||||||
|
* @param availabilityTimeOffsetUs The offset to the current realtime at which segments become
|
||||||
|
* available in microseconds, or {@link C#TIME_UNSET} if not applicable.
|
||||||
* @param initializationTemplate A template defining the location of initialization data, if
|
* @param initializationTemplate A template defining the location of initialization data, if
|
||||||
* such data exists. If non-null then the {@code initialization} parameter is ignored. If
|
* such data exists. If non-null then the {@code initialization} parameter is ignored. If
|
||||||
* null then {@code initialization} will be used.
|
* null then {@code initialization} will be used.
|
||||||
|
|
@ -324,6 +348,7 @@ public abstract class SegmentBase {
|
||||||
long endNumber,
|
long endNumber,
|
||||||
long duration,
|
long duration,
|
||||||
@Nullable List<SegmentTimelineElement> segmentTimeline,
|
@Nullable List<SegmentTimelineElement> segmentTimeline,
|
||||||
|
long availabilityTimeOffsetUs,
|
||||||
@Nullable UrlTemplate initializationTemplate,
|
@Nullable UrlTemplate initializationTemplate,
|
||||||
@Nullable UrlTemplate mediaTemplate) {
|
@Nullable UrlTemplate mediaTemplate) {
|
||||||
super(
|
super(
|
||||||
|
|
@ -332,7 +357,8 @@ public abstract class SegmentBase {
|
||||||
presentationTimeOffset,
|
presentationTimeOffset,
|
||||||
startNumber,
|
startNumber,
|
||||||
duration,
|
duration,
|
||||||
segmentTimeline);
|
segmentTimeline,
|
||||||
|
availabilityTimeOffsetUs);
|
||||||
this.initializationTemplate = initializationTemplate;
|
this.initializationTemplate = initializationTemplate;
|
||||||
this.mediaTemplate = mediaTemplate;
|
this.mediaTemplate = mediaTemplate;
|
||||||
this.endNumber = endNumber;
|
this.endNumber = endNumber;
|
||||||
|
|
|
||||||
|
|
@ -51,6 +51,12 @@ public class DashManifestParserTest {
|
||||||
private static final String SAMPLE_MPD_ASSET_IDENTIFIER = "media/mpd/sample_mpd_asset_identifier";
|
private static final String SAMPLE_MPD_ASSET_IDENTIFIER = "media/mpd/sample_mpd_asset_identifier";
|
||||||
private static final String SAMPLE_MPD_TEXT = "media/mpd/sample_mpd_text";
|
private static final String SAMPLE_MPD_TEXT = "media/mpd/sample_mpd_text";
|
||||||
private static final String SAMPLE_MPD_TRICK_PLAY = "media/mpd/sample_mpd_trick_play";
|
private static final String SAMPLE_MPD_TRICK_PLAY = "media/mpd/sample_mpd_trick_play";
|
||||||
|
private static final String SAMPLE_MPD_AVAILABILITY_TIME_OFFSET_BASE_URL =
|
||||||
|
"media/mpd/sample_mpd_availabilityTimeOffset_baseUrl";
|
||||||
|
private static final String SAMPLE_MPD_AVAILABILITY_TIME_OFFSET_SEGMENT_TEMPLATE =
|
||||||
|
"media/mpd/sample_mpd_availabilityTimeOffset_segmentTemplate";
|
||||||
|
private static final String SAMPLE_MPD_AVAILABILITY_TIME_OFFSET_SEGMENT_LIST =
|
||||||
|
"media/mpd/sample_mpd_availabilityTimeOffset_segmentList";
|
||||||
|
|
||||||
private static final String NEXT_TAG_NAME = "Next";
|
private static final String NEXT_TAG_NAME = "Next";
|
||||||
private static final String NEXT_TAG = "<" + NEXT_TAG_NAME + "/>";
|
private static final String NEXT_TAG = "<" + NEXT_TAG_NAME + "/>";
|
||||||
|
|
@ -469,6 +475,91 @@ public class DashManifestParserTest {
|
||||||
assertThat(assetIdentifier.id).isEqualTo("uniqueId");
|
assertThat(assetIdentifier.id).isEqualTo("uniqueId");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void availabilityTimeOffset_staticManifest_setToTimeUnset() throws IOException {
|
||||||
|
DashManifestParser parser = new DashManifestParser();
|
||||||
|
DashManifest manifest =
|
||||||
|
parser.parse(
|
||||||
|
Uri.parse("https://example.com/test.mpd"),
|
||||||
|
TestUtil.getInputStream(ApplicationProvider.getApplicationContext(), SAMPLE_MPD_TEXT));
|
||||||
|
|
||||||
|
assertThat(manifest.getPeriodCount()).isEqualTo(1);
|
||||||
|
List<AdaptationSet> adaptationSets = manifest.getPeriod(0).adaptationSets;
|
||||||
|
assertThat(adaptationSets).hasSize(3);
|
||||||
|
assertThat(getAvailabilityTimeOffsetUs(adaptationSets.get(0))).isEqualTo(C.TIME_UNSET);
|
||||||
|
assertThat(getAvailabilityTimeOffsetUs(adaptationSets.get(1))).isEqualTo(C.TIME_UNSET);
|
||||||
|
assertThat(getAvailabilityTimeOffsetUs(adaptationSets.get(2))).isEqualTo(C.TIME_UNSET);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void availabilityTimeOffset_dynamicManifest_valuesInBaseUrl_setsCorrectValues()
|
||||||
|
throws IOException {
|
||||||
|
DashManifestParser parser = new DashManifestParser();
|
||||||
|
DashManifest manifest =
|
||||||
|
parser.parse(
|
||||||
|
Uri.parse("https://example.com/test.mpd"),
|
||||||
|
TestUtil.getInputStream(
|
||||||
|
ApplicationProvider.getApplicationContext(),
|
||||||
|
SAMPLE_MPD_AVAILABILITY_TIME_OFFSET_BASE_URL));
|
||||||
|
|
||||||
|
assertThat(manifest.getPeriodCount()).isEqualTo(2);
|
||||||
|
List<AdaptationSet> adaptationSets0 = manifest.getPeriod(0).adaptationSets;
|
||||||
|
List<AdaptationSet> adaptationSets1 = manifest.getPeriod(1).adaptationSets;
|
||||||
|
assertThat(adaptationSets0).hasSize(4);
|
||||||
|
assertThat(adaptationSets1).hasSize(1);
|
||||||
|
assertThat(getAvailabilityTimeOffsetUs(adaptationSets0.get(0))).isEqualTo(5_000_000);
|
||||||
|
assertThat(getAvailabilityTimeOffsetUs(adaptationSets0.get(1))).isEqualTo(4_321_000);
|
||||||
|
assertThat(getAvailabilityTimeOffsetUs(adaptationSets0.get(2))).isEqualTo(9_876_543);
|
||||||
|
assertThat(getAvailabilityTimeOffsetUs(adaptationSets0.get(3))).isEqualTo(C.TIME_UNSET);
|
||||||
|
assertThat(getAvailabilityTimeOffsetUs(adaptationSets1.get(0))).isEqualTo(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void availabilityTimeOffset_dynamicManifest_valuesInSegmentTemplate_setsCorrectValues()
|
||||||
|
throws IOException {
|
||||||
|
DashManifestParser parser = new DashManifestParser();
|
||||||
|
DashManifest manifest =
|
||||||
|
parser.parse(
|
||||||
|
Uri.parse("https://example.com/test.mpd"),
|
||||||
|
TestUtil.getInputStream(
|
||||||
|
ApplicationProvider.getApplicationContext(),
|
||||||
|
SAMPLE_MPD_AVAILABILITY_TIME_OFFSET_SEGMENT_TEMPLATE));
|
||||||
|
|
||||||
|
assertThat(manifest.getPeriodCount()).isEqualTo(2);
|
||||||
|
List<AdaptationSet> adaptationSets0 = manifest.getPeriod(0).adaptationSets;
|
||||||
|
List<AdaptationSet> adaptationSets1 = manifest.getPeriod(1).adaptationSets;
|
||||||
|
assertThat(adaptationSets0).hasSize(4);
|
||||||
|
assertThat(adaptationSets1).hasSize(1);
|
||||||
|
assertThat(getAvailabilityTimeOffsetUs(adaptationSets0.get(0))).isEqualTo(2_000_000);
|
||||||
|
assertThat(getAvailabilityTimeOffsetUs(adaptationSets0.get(1))).isEqualTo(3_210_000);
|
||||||
|
assertThat(getAvailabilityTimeOffsetUs(adaptationSets0.get(2))).isEqualTo(1_230_000);
|
||||||
|
assertThat(getAvailabilityTimeOffsetUs(adaptationSets0.get(3))).isEqualTo(100_000);
|
||||||
|
assertThat(getAvailabilityTimeOffsetUs(adaptationSets1.get(0))).isEqualTo(9_999_000);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void availabilityTimeOffset_dynamicManifest_valuesInSegmentList_setsCorrectValues()
|
||||||
|
throws IOException {
|
||||||
|
DashManifestParser parser = new DashManifestParser();
|
||||||
|
DashManifest manifest =
|
||||||
|
parser.parse(
|
||||||
|
Uri.parse("https://example.com/test.mpd"),
|
||||||
|
TestUtil.getInputStream(
|
||||||
|
ApplicationProvider.getApplicationContext(),
|
||||||
|
SAMPLE_MPD_AVAILABILITY_TIME_OFFSET_SEGMENT_LIST));
|
||||||
|
|
||||||
|
assertThat(manifest.getPeriodCount()).isEqualTo(2);
|
||||||
|
List<AdaptationSet> adaptationSets0 = manifest.getPeriod(0).adaptationSets;
|
||||||
|
List<AdaptationSet> adaptationSets1 = manifest.getPeriod(1).adaptationSets;
|
||||||
|
assertThat(adaptationSets0).hasSize(4);
|
||||||
|
assertThat(adaptationSets1).hasSize(1);
|
||||||
|
assertThat(getAvailabilityTimeOffsetUs(adaptationSets0.get(0))).isEqualTo(2_000_000);
|
||||||
|
assertThat(getAvailabilityTimeOffsetUs(adaptationSets0.get(1))).isEqualTo(3_210_000);
|
||||||
|
assertThat(getAvailabilityTimeOffsetUs(adaptationSets0.get(2))).isEqualTo(1_230_000);
|
||||||
|
assertThat(getAvailabilityTimeOffsetUs(adaptationSets0.get(3))).isEqualTo(100_000);
|
||||||
|
assertThat(getAvailabilityTimeOffsetUs(adaptationSets1.get(0))).isEqualTo(9_999_000);
|
||||||
|
}
|
||||||
|
|
||||||
private static List<Descriptor> buildCea608AccessibilityDescriptors(String value) {
|
private static List<Descriptor> buildCea608AccessibilityDescriptors(String value) {
|
||||||
return Collections.singletonList(new Descriptor("urn:scte:dash:cc:cea-608:2015", value, null));
|
return Collections.singletonList(new Descriptor("urn:scte:dash:cc:cea-608:2015", value, null));
|
||||||
}
|
}
|
||||||
|
|
@ -482,4 +573,13 @@ public class DashManifestParserTest {
|
||||||
assertThat(xpp.getEventType()).isEqualTo(XmlPullParser.START_TAG);
|
assertThat(xpp.getEventType()).isEqualTo(XmlPullParser.START_TAG);
|
||||||
assertThat(xpp.getName()).isEqualTo(NEXT_TAG_NAME);
|
assertThat(xpp.getName()).isEqualTo(NEXT_TAG_NAME);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static long getAvailabilityTimeOffsetUs(AdaptationSet adaptationSet) {
|
||||||
|
assertThat(adaptationSet.representations).isNotEmpty();
|
||||||
|
Representation representation = adaptationSet.representations.get(0);
|
||||||
|
assertThat(representation).isInstanceOf(Representation.MultiSegmentRepresentation.class);
|
||||||
|
SegmentBase.MultiSegmentBase segmentBase =
|
||||||
|
((Representation.MultiSegmentRepresentation) representation).segmentBase;
|
||||||
|
return segmentBase.availabilityTimeOffsetUs;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
40
testdata/src/test/assets/media/mpd/sample_mpd_availabilityTimeOffset_baseUrl
vendored
Normal file
40
testdata/src/test/assets/media/mpd/sample_mpd_availabilityTimeOffset_baseUrl
vendored
Normal file
|
|
@ -0,0 +1,40 @@
|
||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<MPD xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||||
|
xmlns="urn:mpeg:DASH:schema:MPD:2011"
|
||||||
|
xsi:schemaLocation="urn:mpeg:DASH:schema:MPD:2011 DASH-MPD.xsd"
|
||||||
|
profiles="urn:mpeg:dash:profile:isoff-main:2011"
|
||||||
|
type="dynamic"
|
||||||
|
availabilityStartTime="2016-10-14T17:00:17">
|
||||||
|
<Period start="PT0.000S">
|
||||||
|
<BaseURL availabilityTimeOffset="5">http://video.com/baseUrl</BaseURL>
|
||||||
|
<SegmentTemplate/>
|
||||||
|
<AdaptationSet>
|
||||||
|
<Representation/>
|
||||||
|
</AdaptationSet>
|
||||||
|
<AdaptationSet>
|
||||||
|
<BaseURL availabilityTimeOffset="4.321">http://video.com/baseUrl</BaseURL>
|
||||||
|
<SegmentTemplate/>
|
||||||
|
<Representation/>
|
||||||
|
</AdaptationSet>
|
||||||
|
<AdaptationSet>
|
||||||
|
<SegmentTemplate/>
|
||||||
|
<Representation>
|
||||||
|
<BaseURL availabilityTimeOffset="9.876543210">http://video.com/baseUrl</BaseURL>
|
||||||
|
<SegmentTemplate/>
|
||||||
|
</Representation>
|
||||||
|
</AdaptationSet>
|
||||||
|
<AdaptationSet>
|
||||||
|
<BaseURL availabilityTimeOffset="0.5">http://video.com/baseUrl</BaseURL>
|
||||||
|
<Representation>
|
||||||
|
<BaseURL availabilityTimeOffset="INF">http://video.com/baseUrl</BaseURL>
|
||||||
|
<SegmentTemplate/>
|
||||||
|
</Representation>
|
||||||
|
</AdaptationSet>
|
||||||
|
</Period>
|
||||||
|
<Period start="PT100.000S">
|
||||||
|
<SegmentTemplate/>
|
||||||
|
<AdaptationSet>
|
||||||
|
<Representation/>
|
||||||
|
</AdaptationSet>
|
||||||
|
</Period>
|
||||||
|
</MPD>
|
||||||
46
testdata/src/test/assets/media/mpd/sample_mpd_availabilityTimeOffset_segmentList
vendored
Normal file
46
testdata/src/test/assets/media/mpd/sample_mpd_availabilityTimeOffset_segmentList
vendored
Normal file
|
|
@ -0,0 +1,46 @@
|
||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<MPD xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||||
|
xmlns="urn:mpeg:DASH:schema:MPD:2011"
|
||||||
|
xsi:schemaLocation="urn:mpeg:DASH:schema:MPD:2011 DASH-MPD.xsd"
|
||||||
|
profiles="urn:mpeg:dash:profile:isoff-main:2011"
|
||||||
|
type="dynamic"
|
||||||
|
availabilityStartTime="2016-10-14T17:00:17">
|
||||||
|
<BaseURL availabilityTimeOffset="9.999">http://video.com/baseUrl</BaseURL>
|
||||||
|
<Period start="PT0.000S">
|
||||||
|
<BaseURL availabilityTimeOffset="9.999">http://video.com/baseUrl</BaseURL>
|
||||||
|
<SegmentList availabilityTimeOffset="2"/>
|
||||||
|
<AdaptationSet>
|
||||||
|
<Representation>
|
||||||
|
<BaseURL availabilityTimeOffset="9.999">http://video.com/baseUrl</BaseURL>
|
||||||
|
<SegmentList/>
|
||||||
|
</Representation>
|
||||||
|
</AdaptationSet>
|
||||||
|
<AdaptationSet>
|
||||||
|
<BaseURL availabilityTimeOffset="9.999">http://video.com/baseUrl</BaseURL>
|
||||||
|
<SegmentList availabilityTimeOffset="3.21"/>
|
||||||
|
<Representation/>
|
||||||
|
</AdaptationSet>
|
||||||
|
<AdaptationSet>
|
||||||
|
<SegmentList/>
|
||||||
|
<Representation>
|
||||||
|
<BaseURL availabilityTimeOffset="9.999">http://video.com/baseUrl</BaseURL>
|
||||||
|
<SegmentList availabilityTimeOffset="1.23"/>
|
||||||
|
</Representation>
|
||||||
|
</AdaptationSet>
|
||||||
|
<AdaptationSet>
|
||||||
|
<BaseURL availabilityTimeOffset="9.999">http://video.com/baseUrl</BaseURL>
|
||||||
|
<SegmentList availabilityTimeOffset="123"/>
|
||||||
|
<Representation>
|
||||||
|
<BaseURL availabilityTimeOffset="9.999">http://video.com/baseUrl</BaseURL>
|
||||||
|
<SegmentList availabilityTimeOffset="0.1"/>
|
||||||
|
</Representation>
|
||||||
|
</AdaptationSet>
|
||||||
|
</Period>
|
||||||
|
<Period start="PT200.000S">
|
||||||
|
<AdaptationSet>
|
||||||
|
<Representation>
|
||||||
|
<SegmentList/>
|
||||||
|
</Representation>
|
||||||
|
</AdaptationSet>
|
||||||
|
</Period>
|
||||||
|
</MPD>
|
||||||
46
testdata/src/test/assets/media/mpd/sample_mpd_availabilityTimeOffset_segmentTemplate
vendored
Normal file
46
testdata/src/test/assets/media/mpd/sample_mpd_availabilityTimeOffset_segmentTemplate
vendored
Normal file
|
|
@ -0,0 +1,46 @@
|
||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<MPD xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||||
|
xmlns="urn:mpeg:DASH:schema:MPD:2011"
|
||||||
|
xsi:schemaLocation="urn:mpeg:DASH:schema:MPD:2011 DASH-MPD.xsd"
|
||||||
|
profiles="urn:mpeg:dash:profile:isoff-main:2011"
|
||||||
|
type="dynamic"
|
||||||
|
availabilityStartTime="2016-10-14T17:00:17">
|
||||||
|
<BaseURL availabilityTimeOffset="9.999">http://video.com/baseUrl</BaseURL>
|
||||||
|
<Period start="PT0.000S">
|
||||||
|
<BaseURL availabilityTimeOffset="9.999">http://video.com/baseUrl</BaseURL>
|
||||||
|
<SegmentTemplate availabilityTimeOffset="2"/>
|
||||||
|
<AdaptationSet>
|
||||||
|
<Representation>
|
||||||
|
<BaseURL availabilityTimeOffset="9.999">http://video.com/baseUrl</BaseURL>
|
||||||
|
<SegmentTemplate/>
|
||||||
|
</Representation>
|
||||||
|
</AdaptationSet>
|
||||||
|
<AdaptationSet>
|
||||||
|
<BaseURL availabilityTimeOffset="9.999">http://video.com/baseUrl</BaseURL>
|
||||||
|
<SegmentTemplate availabilityTimeOffset="3.21"/>
|
||||||
|
<Representation/>
|
||||||
|
</AdaptationSet>
|
||||||
|
<AdaptationSet>
|
||||||
|
<SegmentTemplate/>
|
||||||
|
<Representation>
|
||||||
|
<BaseURL availabilityTimeOffset="9.999">http://video.com/baseUrl</BaseURL>
|
||||||
|
<SegmentTemplate availabilityTimeOffset="1.23"/>
|
||||||
|
</Representation>
|
||||||
|
</AdaptationSet>
|
||||||
|
<AdaptationSet>
|
||||||
|
<BaseURL availabilityTimeOffset="9.999">http://video.com/baseUrl</BaseURL>
|
||||||
|
<SegmentTemplate availabilityTimeOffset="123"/>
|
||||||
|
<Representation>
|
||||||
|
<BaseURL availabilityTimeOffset="9.999">http://video.com/baseUrl</BaseURL>
|
||||||
|
<SegmentTemplate availabilityTimeOffset="0.1"/>
|
||||||
|
</Representation>
|
||||||
|
</AdaptationSet>
|
||||||
|
</Period>
|
||||||
|
<Period start="PT200.000S">
|
||||||
|
<AdaptationSet>
|
||||||
|
<Representation>
|
||||||
|
<SegmentTemplate/>
|
||||||
|
</Representation>
|
||||||
|
</AdaptationSet>
|
||||||
|
</Period>
|
||||||
|
</MPD>
|
||||||
Loading…
Reference in a new issue