mirror of
https://github.com/samsonjs/media.git
synced 2026-04-27 15:07:40 +00:00
Ensure implicit manifest updates arrives asap
PiperOrigin-RevId: 333714978
This commit is contained in:
parent
397fe8f305
commit
c107017a4b
8 changed files with 287 additions and 151 deletions
|
|
@ -50,6 +50,8 @@ import com.google.android.exoplayer2.source.dash.PlayerEmsgHandler.PlayerEmsgCal
|
||||||
import com.google.android.exoplayer2.source.dash.manifest.AdaptationSet;
|
import com.google.android.exoplayer2.source.dash.manifest.AdaptationSet;
|
||||||
import com.google.android.exoplayer2.source.dash.manifest.DashManifest;
|
import com.google.android.exoplayer2.source.dash.manifest.DashManifest;
|
||||||
import com.google.android.exoplayer2.source.dash.manifest.DashManifestParser;
|
import com.google.android.exoplayer2.source.dash.manifest.DashManifestParser;
|
||||||
|
import com.google.android.exoplayer2.source.dash.manifest.Period;
|
||||||
|
import com.google.android.exoplayer2.source.dash.manifest.Representation;
|
||||||
import com.google.android.exoplayer2.source.dash.manifest.UtcTimingElement;
|
import com.google.android.exoplayer2.source.dash.manifest.UtcTimingElement;
|
||||||
import com.google.android.exoplayer2.upstream.Allocator;
|
import com.google.android.exoplayer2.upstream.Allocator;
|
||||||
import com.google.android.exoplayer2.upstream.DataSource;
|
import com.google.android.exoplayer2.upstream.DataSource;
|
||||||
|
|
@ -68,10 +70,12 @@ import com.google.android.exoplayer2.util.MimeTypes;
|
||||||
import com.google.android.exoplayer2.util.SntpClient;
|
import com.google.android.exoplayer2.util.SntpClient;
|
||||||
import com.google.android.exoplayer2.util.Util;
|
import com.google.android.exoplayer2.util.Util;
|
||||||
import com.google.common.base.Charsets;
|
import com.google.common.base.Charsets;
|
||||||
|
import com.google.common.math.LongMath;
|
||||||
import java.io.BufferedReader;
|
import java.io.BufferedReader;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.io.InputStream;
|
import java.io.InputStream;
|
||||||
import java.io.InputStreamReader;
|
import java.io.InputStreamReader;
|
||||||
|
import java.math.RoundingMode;
|
||||||
import java.text.ParseException;
|
import java.text.ParseException;
|
||||||
import java.text.SimpleDateFormat;
|
import java.text.SimpleDateFormat;
|
||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
|
|
@ -441,7 +445,7 @@ public final class DashMediaSource extends BaseMediaSource {
|
||||||
* MediaSourceCaller#onSourceInfoRefreshed(MediaSource, Timeline)} when the source's {@link
|
* MediaSourceCaller#onSourceInfoRefreshed(MediaSource, Timeline)} when the source's {@link
|
||||||
* Timeline} is changing dynamically (for example, for incomplete live streams).
|
* Timeline} is changing dynamically (for example, for incomplete live streams).
|
||||||
*/
|
*/
|
||||||
private static final int NOTIFY_MANIFEST_INTERVAL_MS = 5000;
|
private static final long DEFAULT_NOTIFY_MANIFEST_INTERVAL_MS = 5000;
|
||||||
/**
|
/**
|
||||||
* The minimum default start position for live streams, relative to the start of the live window.
|
* The minimum default start position for live streams, relative to the start of the live window.
|
||||||
*/
|
*/
|
||||||
|
|
@ -1106,7 +1110,10 @@ public final class DashMediaSource extends BaseMediaSource {
|
||||||
handler.removeCallbacks(simulateManifestRefreshRunnable);
|
handler.removeCallbacks(simulateManifestRefreshRunnable);
|
||||||
// If the window is changing implicitly, post a simulated manifest refresh to update it.
|
// If the window is changing implicitly, post a simulated manifest refresh to update it.
|
||||||
if (windowChangingImplicitly) {
|
if (windowChangingImplicitly) {
|
||||||
handler.postDelayed(simulateManifestRefreshRunnable, NOTIFY_MANIFEST_INTERVAL_MS);
|
handler.postDelayed(
|
||||||
|
simulateManifestRefreshRunnable,
|
||||||
|
getIntervalUntilNextManifestRefreshMs(
|
||||||
|
manifest, Util.getNowUnixTimeMs(elapsedRealtimeOffsetMs)));
|
||||||
}
|
}
|
||||||
if (manifestLoadPending) {
|
if (manifestLoadPending) {
|
||||||
startLoadingManifest();
|
startLoadingManifest();
|
||||||
|
|
@ -1165,6 +1172,38 @@ public final class DashMediaSource extends BaseMediaSource {
|
||||||
loadable.type);
|
loadable.type);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static long getIntervalUntilNextManifestRefreshMs(
|
||||||
|
DashManifest manifest, long nowUnixTimeMs) {
|
||||||
|
int periodIndex = manifest.getPeriodCount() - 1;
|
||||||
|
Period period = manifest.getPeriod(periodIndex);
|
||||||
|
long periodStartUs = C.msToUs(period.startMs);
|
||||||
|
long periodDurationUs = manifest.getPeriodDurationUs(periodIndex);
|
||||||
|
long nowUnixTimeUs = C.msToUs(nowUnixTimeMs);
|
||||||
|
long availabilityStartTimeUs = C.msToUs(manifest.availabilityStartTimeMs);
|
||||||
|
long intervalUs = C.msToUs(DEFAULT_NOTIFY_MANIFEST_INTERVAL_MS);
|
||||||
|
for (int i = 0; i < period.adaptationSets.size(); i++) {
|
||||||
|
List<Representation> representations = period.adaptationSets.get(i).representations;
|
||||||
|
if (representations.isEmpty()) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
@Nullable DashSegmentIndex index = representations.get(0).getIndex();
|
||||||
|
if (index != null) {
|
||||||
|
long nextSegmentShiftUnixTimeUs =
|
||||||
|
availabilityStartTimeUs
|
||||||
|
+ periodStartUs
|
||||||
|
+ index.getNextSegmentAvailableTimeUs(periodDurationUs, nowUnixTimeUs);
|
||||||
|
long requiredIntervalUs = nextSegmentShiftUnixTimeUs - nowUnixTimeUs;
|
||||||
|
// Avoid multiple refreshes within a very small amount of time.
|
||||||
|
if (requiredIntervalUs < intervalUs - 100_000
|
||||||
|
|| (requiredIntervalUs > intervalUs && requiredIntervalUs < intervalUs + 100_000)) {
|
||||||
|
intervalUs = requiredIntervalUs;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Round up to compensate for a potential loss in the us to ms conversion.
|
||||||
|
return LongMath.divide(intervalUs, 1000, RoundingMode.CEILING);
|
||||||
|
}
|
||||||
|
|
||||||
private static final class PeriodSeekInfo {
|
private static final class PeriodSeekInfo {
|
||||||
|
|
||||||
public static PeriodSeekInfo createPeriodSeekInfo(
|
public static PeriodSeekInfo createPeriodSeekInfo(
|
||||||
|
|
|
||||||
|
|
@ -101,6 +101,18 @@ public interface DashSegmentIndex {
|
||||||
*/
|
*/
|
||||||
int getAvailableSegmentCount(long periodDurationUs, long nowUnixTimeUs);
|
int getAvailableSegmentCount(long periodDurationUs, long nowUnixTimeUs);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the time, in microseconds, at which a new segment becomes available, or {@link
|
||||||
|
* C#TIME_UNSET} if not applicable.
|
||||||
|
*
|
||||||
|
* @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 time, in microseconds, at which a new segment becomes available, or {@link
|
||||||
|
* C#TIME_UNSET} if not applicable.
|
||||||
|
*/
|
||||||
|
long getNextSegmentAvailableTimeUs(long periodDurationUs, long nowUnixTimeUs);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns true if segments are defined explicitly by the index.
|
* Returns true if segments are defined explicitly by the index.
|
||||||
*
|
*
|
||||||
|
|
|
||||||
|
|
@ -15,6 +15,7 @@
|
||||||
*/
|
*/
|
||||||
package com.google.android.exoplayer2.source.dash;
|
package com.google.android.exoplayer2.source.dash;
|
||||||
|
|
||||||
|
import com.google.android.exoplayer2.C;
|
||||||
import com.google.android.exoplayer2.extractor.ChunkIndex;
|
import com.google.android.exoplayer2.extractor.ChunkIndex;
|
||||||
import com.google.android.exoplayer2.source.dash.manifest.RangedUri;
|
import com.google.android.exoplayer2.source.dash.manifest.RangedUri;
|
||||||
|
|
||||||
|
|
@ -56,6 +57,11 @@ public final class DashWrappingSegmentIndex implements DashSegmentIndex {
|
||||||
return chunkIndex.length;
|
return chunkIndex.length;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public long getNextSegmentAvailableTimeUs(long periodDurationUs, long nowUnixTimeUs) {
|
||||||
|
return C.TIME_UNSET;
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public long getTimeUs(long segmentNum) {
|
public long getTimeUs(long segmentNum) {
|
||||||
return chunkIndex.timesUs[(int) segmentNum] - timeOffsetUs;
|
return chunkIndex.timesUs[(int) segmentNum] - timeOffsetUs;
|
||||||
|
|
|
||||||
|
|
@ -291,9 +291,11 @@ public class DashManifestParser extends DefaultHandler
|
||||||
parseSegmentList(
|
parseSegmentList(
|
||||||
xpp,
|
xpp,
|
||||||
/* parent= */ null,
|
/* parent= */ null,
|
||||||
|
periodStartUnixTimeMs,
|
||||||
durationMs,
|
durationMs,
|
||||||
baseUrlAvailabilityTimeOffsetUs,
|
baseUrlAvailabilityTimeOffsetUs,
|
||||||
segmentBaseAvailabilityTimeOffsetUs);
|
segmentBaseAvailabilityTimeOffsetUs,
|
||||||
|
timeShiftBufferDepthMs);
|
||||||
} else if (XmlPullParserUtil.isStartTag(xpp, "SegmentTemplate")) {
|
} else if (XmlPullParserUtil.isStartTag(xpp, "SegmentTemplate")) {
|
||||||
segmentBaseAvailabilityTimeOffsetUs =
|
segmentBaseAvailabilityTimeOffsetUs =
|
||||||
parseAvailabilityTimeOffsetUs(xpp, /* parentAvailabilityTimeOffsetUs= */ C.TIME_UNSET);
|
parseAvailabilityTimeOffsetUs(xpp, /* parentAvailabilityTimeOffsetUs= */ C.TIME_UNSET);
|
||||||
|
|
@ -302,9 +304,11 @@ public class DashManifestParser extends DefaultHandler
|
||||||
xpp,
|
xpp,
|
||||||
/* parent= */ null,
|
/* parent= */ null,
|
||||||
ImmutableList.of(),
|
ImmutableList.of(),
|
||||||
|
periodStartUnixTimeMs,
|
||||||
durationMs,
|
durationMs,
|
||||||
baseUrlAvailabilityTimeOffsetUs,
|
baseUrlAvailabilityTimeOffsetUs,
|
||||||
segmentBaseAvailabilityTimeOffsetUs);
|
segmentBaseAvailabilityTimeOffsetUs,
|
||||||
|
timeShiftBufferDepthMs);
|
||||||
} else if (XmlPullParserUtil.isStartTag(xpp, "AssetIdentifier")) {
|
} else if (XmlPullParserUtil.isStartTag(xpp, "AssetIdentifier")) {
|
||||||
assetIdentifier = parseDescriptor(xpp, "AssetIdentifier");
|
assetIdentifier = parseDescriptor(xpp, "AssetIdentifier");
|
||||||
} else {
|
} else {
|
||||||
|
|
@ -407,9 +411,11 @@ public class DashManifestParser extends DefaultHandler
|
||||||
essentialProperties,
|
essentialProperties,
|
||||||
supplementalProperties,
|
supplementalProperties,
|
||||||
segmentBase,
|
segmentBase,
|
||||||
|
periodStartUnixTimeMs,
|
||||||
periodDurationMs,
|
periodDurationMs,
|
||||||
baseUrlAvailabilityTimeOffsetUs,
|
baseUrlAvailabilityTimeOffsetUs,
|
||||||
segmentBaseAvailabilityTimeOffsetUs);
|
segmentBaseAvailabilityTimeOffsetUs,
|
||||||
|
timeShiftBufferDepthMs);
|
||||||
contentType =
|
contentType =
|
||||||
checkContentTypeConsistency(
|
checkContentTypeConsistency(
|
||||||
contentType, MimeTypes.getTrackType(representationInfo.format.sampleMimeType));
|
contentType, MimeTypes.getTrackType(representationInfo.format.sampleMimeType));
|
||||||
|
|
@ -423,9 +429,11 @@ public class DashManifestParser extends DefaultHandler
|
||||||
parseSegmentList(
|
parseSegmentList(
|
||||||
xpp,
|
xpp,
|
||||||
(SegmentList) segmentBase,
|
(SegmentList) segmentBase,
|
||||||
|
periodStartUnixTimeMs,
|
||||||
periodDurationMs,
|
periodDurationMs,
|
||||||
baseUrlAvailabilityTimeOffsetUs,
|
baseUrlAvailabilityTimeOffsetUs,
|
||||||
segmentBaseAvailabilityTimeOffsetUs);
|
segmentBaseAvailabilityTimeOffsetUs,
|
||||||
|
timeShiftBufferDepthMs);
|
||||||
} else if (XmlPullParserUtil.isStartTag(xpp, "SegmentTemplate")) {
|
} else if (XmlPullParserUtil.isStartTag(xpp, "SegmentTemplate")) {
|
||||||
segmentBaseAvailabilityTimeOffsetUs =
|
segmentBaseAvailabilityTimeOffsetUs =
|
||||||
parseAvailabilityTimeOffsetUs(xpp, segmentBaseAvailabilityTimeOffsetUs);
|
parseAvailabilityTimeOffsetUs(xpp, segmentBaseAvailabilityTimeOffsetUs);
|
||||||
|
|
@ -434,9 +442,11 @@ public class DashManifestParser extends DefaultHandler
|
||||||
xpp,
|
xpp,
|
||||||
(SegmentTemplate) segmentBase,
|
(SegmentTemplate) segmentBase,
|
||||||
supplementalProperties,
|
supplementalProperties,
|
||||||
|
periodStartUnixTimeMs,
|
||||||
periodDurationMs,
|
periodDurationMs,
|
||||||
baseUrlAvailabilityTimeOffsetUs,
|
baseUrlAvailabilityTimeOffsetUs,
|
||||||
segmentBaseAvailabilityTimeOffsetUs);
|
segmentBaseAvailabilityTimeOffsetUs,
|
||||||
|
timeShiftBufferDepthMs);
|
||||||
} 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")) {
|
||||||
|
|
@ -455,9 +465,7 @@ public class DashManifestParser extends DefaultHandler
|
||||||
label,
|
label,
|
||||||
drmSchemeType,
|
drmSchemeType,
|
||||||
drmSchemeDatas,
|
drmSchemeDatas,
|
||||||
inbandEventStreams,
|
inbandEventStreams));
|
||||||
periodStartUnixTimeMs,
|
|
||||||
timeShiftBufferDepthMs));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return buildAdaptationSet(
|
return buildAdaptationSet(
|
||||||
|
|
@ -599,9 +607,11 @@ public class DashManifestParser extends DefaultHandler
|
||||||
List<Descriptor> adaptationSetEssentialProperties,
|
List<Descriptor> adaptationSetEssentialProperties,
|
||||||
List<Descriptor> adaptationSetSupplementalProperties,
|
List<Descriptor> adaptationSetSupplementalProperties,
|
||||||
@Nullable SegmentBase segmentBase,
|
@Nullable SegmentBase segmentBase,
|
||||||
|
long periodStartUnixTimeMs,
|
||||||
long periodDurationMs,
|
long periodDurationMs,
|
||||||
long baseUrlAvailabilityTimeOffsetUs,
|
long baseUrlAvailabilityTimeOffsetUs,
|
||||||
long segmentBaseAvailabilityTimeOffsetUs)
|
long segmentBaseAvailabilityTimeOffsetUs,
|
||||||
|
long timeShiftBufferDepthMs)
|
||||||
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);
|
||||||
|
|
@ -641,9 +651,11 @@ public class DashManifestParser extends DefaultHandler
|
||||||
parseSegmentList(
|
parseSegmentList(
|
||||||
xpp,
|
xpp,
|
||||||
(SegmentList) segmentBase,
|
(SegmentList) segmentBase,
|
||||||
|
periodStartUnixTimeMs,
|
||||||
periodDurationMs,
|
periodDurationMs,
|
||||||
baseUrlAvailabilityTimeOffsetUs,
|
baseUrlAvailabilityTimeOffsetUs,
|
||||||
segmentBaseAvailabilityTimeOffsetUs);
|
segmentBaseAvailabilityTimeOffsetUs,
|
||||||
|
timeShiftBufferDepthMs);
|
||||||
} else if (XmlPullParserUtil.isStartTag(xpp, "SegmentTemplate")) {
|
} else if (XmlPullParserUtil.isStartTag(xpp, "SegmentTemplate")) {
|
||||||
segmentBaseAvailabilityTimeOffsetUs =
|
segmentBaseAvailabilityTimeOffsetUs =
|
||||||
parseAvailabilityTimeOffsetUs(xpp, segmentBaseAvailabilityTimeOffsetUs);
|
parseAvailabilityTimeOffsetUs(xpp, segmentBaseAvailabilityTimeOffsetUs);
|
||||||
|
|
@ -652,9 +664,11 @@ public class DashManifestParser extends DefaultHandler
|
||||||
xpp,
|
xpp,
|
||||||
(SegmentTemplate) segmentBase,
|
(SegmentTemplate) segmentBase,
|
||||||
adaptationSetSupplementalProperties,
|
adaptationSetSupplementalProperties,
|
||||||
|
periodStartUnixTimeMs,
|
||||||
periodDurationMs,
|
periodDurationMs,
|
||||||
baseUrlAvailabilityTimeOffsetUs,
|
baseUrlAvailabilityTimeOffsetUs,
|
||||||
segmentBaseAvailabilityTimeOffsetUs);
|
segmentBaseAvailabilityTimeOffsetUs,
|
||||||
|
timeShiftBufferDepthMs);
|
||||||
} 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) {
|
||||||
|
|
@ -754,9 +768,7 @@ public class DashManifestParser extends DefaultHandler
|
||||||
@Nullable String label,
|
@Nullable String label,
|
||||||
@Nullable String extraDrmSchemeType,
|
@Nullable String extraDrmSchemeType,
|
||||||
ArrayList<SchemeData> extraDrmSchemeDatas,
|
ArrayList<SchemeData> extraDrmSchemeDatas,
|
||||||
ArrayList<Descriptor> extraInbandEventStreams,
|
ArrayList<Descriptor> extraInbandEventStreams) {
|
||||||
long periodStartUnixTimeMs,
|
|
||||||
long timeShiftBufferDepthMs) {
|
|
||||||
Format.Builder formatBuilder = representationInfo.format.buildUpon();
|
Format.Builder formatBuilder = representationInfo.format.buildUpon();
|
||||||
if (label != null) {
|
if (label != null) {
|
||||||
formatBuilder.setLabel(label);
|
formatBuilder.setLabel(label);
|
||||||
|
|
@ -778,9 +790,7 @@ public class DashManifestParser extends DefaultHandler
|
||||||
formatBuilder.build(),
|
formatBuilder.build(),
|
||||||
representationInfo.baseUrl,
|
representationInfo.baseUrl,
|
||||||
representationInfo.segmentBase,
|
representationInfo.segmentBase,
|
||||||
inbandEventStreams,
|
inbandEventStreams);
|
||||||
periodStartUnixTimeMs,
|
|
||||||
timeShiftBufferDepthMs);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// SegmentBase, SegmentList and SegmentTemplate parsing.
|
// SegmentBase, SegmentList and SegmentTemplate parsing.
|
||||||
|
|
@ -825,9 +835,11 @@ public class DashManifestParser extends DefaultHandler
|
||||||
protected SegmentList parseSegmentList(
|
protected SegmentList parseSegmentList(
|
||||||
XmlPullParser xpp,
|
XmlPullParser xpp,
|
||||||
@Nullable SegmentList parent,
|
@Nullable SegmentList parent,
|
||||||
|
long periodStartUnixTimeMs,
|
||||||
long periodDurationMs,
|
long periodDurationMs,
|
||||||
long baseUrlAvailabilityTimeOffsetUs,
|
long baseUrlAvailabilityTimeOffsetUs,
|
||||||
long segmentBaseAvailabilityTimeOffsetUs)
|
long segmentBaseAvailabilityTimeOffsetUs,
|
||||||
|
long timeShiftBufferDepthMs)
|
||||||
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);
|
||||||
|
|
@ -873,7 +885,9 @@ public class DashManifestParser extends DefaultHandler
|
||||||
duration,
|
duration,
|
||||||
timeline,
|
timeline,
|
||||||
availabilityTimeOffsetUs,
|
availabilityTimeOffsetUs,
|
||||||
segments);
|
segments,
|
||||||
|
timeShiftBufferDepthMs,
|
||||||
|
periodStartUnixTimeMs);
|
||||||
}
|
}
|
||||||
|
|
||||||
protected SegmentList buildSegmentList(
|
protected SegmentList buildSegmentList(
|
||||||
|
|
@ -884,7 +898,9 @@ public class DashManifestParser extends DefaultHandler
|
||||||
long duration,
|
long duration,
|
||||||
@Nullable List<SegmentTimelineElement> timeline,
|
@Nullable List<SegmentTimelineElement> timeline,
|
||||||
long availabilityTimeOffsetUs,
|
long availabilityTimeOffsetUs,
|
||||||
@Nullable List<RangedUri> segments) {
|
@Nullable List<RangedUri> segments,
|
||||||
|
long timeShiftBufferDepthMs,
|
||||||
|
long periodStartUnixTimeMs) {
|
||||||
return new SegmentList(
|
return new SegmentList(
|
||||||
initialization,
|
initialization,
|
||||||
timescale,
|
timescale,
|
||||||
|
|
@ -893,16 +909,20 @@ public class DashManifestParser extends DefaultHandler
|
||||||
duration,
|
duration,
|
||||||
timeline,
|
timeline,
|
||||||
availabilityTimeOffsetUs,
|
availabilityTimeOffsetUs,
|
||||||
segments);
|
segments,
|
||||||
|
C.msToUs(timeShiftBufferDepthMs),
|
||||||
|
C.msToUs(periodStartUnixTimeMs));
|
||||||
}
|
}
|
||||||
|
|
||||||
protected SegmentTemplate parseSegmentTemplate(
|
protected SegmentTemplate parseSegmentTemplate(
|
||||||
XmlPullParser xpp,
|
XmlPullParser xpp,
|
||||||
@Nullable SegmentTemplate parent,
|
@Nullable SegmentTemplate parent,
|
||||||
List<Descriptor> adaptationSetSupplementalProperties,
|
List<Descriptor> adaptationSetSupplementalProperties,
|
||||||
|
long periodStartUnixTimeMs,
|
||||||
long periodDurationMs,
|
long periodDurationMs,
|
||||||
long baseUrlAvailabilityTimeOffsetUs,
|
long baseUrlAvailabilityTimeOffsetUs,
|
||||||
long segmentBaseAvailabilityTimeOffsetUs)
|
long segmentBaseAvailabilityTimeOffsetUs,
|
||||||
|
long timeShiftBufferDepthMs)
|
||||||
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",
|
||||||
|
|
@ -949,7 +969,9 @@ public class DashManifestParser extends DefaultHandler
|
||||||
timeline,
|
timeline,
|
||||||
availabilityTimeOffsetUs,
|
availabilityTimeOffsetUs,
|
||||||
initializationTemplate,
|
initializationTemplate,
|
||||||
mediaTemplate);
|
mediaTemplate,
|
||||||
|
timeShiftBufferDepthMs,
|
||||||
|
periodStartUnixTimeMs);
|
||||||
}
|
}
|
||||||
|
|
||||||
protected SegmentTemplate buildSegmentTemplate(
|
protected SegmentTemplate buildSegmentTemplate(
|
||||||
|
|
@ -962,7 +984,9 @@ public class DashManifestParser extends DefaultHandler
|
||||||
List<SegmentTimelineElement> timeline,
|
List<SegmentTimelineElement> timeline,
|
||||||
long availabilityTimeOffsetUs,
|
long availabilityTimeOffsetUs,
|
||||||
@Nullable UrlTemplate initializationTemplate,
|
@Nullable UrlTemplate initializationTemplate,
|
||||||
@Nullable UrlTemplate mediaTemplate) {
|
@Nullable UrlTemplate mediaTemplate,
|
||||||
|
long timeShiftBufferDepthMs,
|
||||||
|
long periodStartUnixTimeMs) {
|
||||||
return new SegmentTemplate(
|
return new SegmentTemplate(
|
||||||
initialization,
|
initialization,
|
||||||
timescale,
|
timescale,
|
||||||
|
|
@ -973,7 +997,9 @@ public class DashManifestParser extends DefaultHandler
|
||||||
timeline,
|
timeline,
|
||||||
availabilityTimeOffsetUs,
|
availabilityTimeOffsetUs,
|
||||||
initializationTemplate,
|
initializationTemplate,
|
||||||
mediaTemplate);
|
mediaTemplate,
|
||||||
|
C.msToUs(timeShiftBufferDepthMs),
|
||||||
|
C.msToUs(periodStartUnixTimeMs));
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
||||||
|
|
@ -15,8 +15,6 @@
|
||||||
*/
|
*/
|
||||||
package com.google.android.exoplayer2.source.dash.manifest;
|
package com.google.android.exoplayer2.source.dash.manifest;
|
||||||
|
|
||||||
import static java.lang.Math.max;
|
|
||||||
|
|
||||||
import android.net.Uri;
|
import android.net.Uri;
|
||||||
import androidx.annotation.Nullable;
|
import androidx.annotation.Nullable;
|
||||||
import androidx.annotation.VisibleForTesting;
|
import androidx.annotation.VisibleForTesting;
|
||||||
|
|
@ -73,14 +71,7 @@ public abstract class Representation {
|
||||||
*/
|
*/
|
||||||
public static Representation newInstance(
|
public static Representation newInstance(
|
||||||
long revisionId, Format format, String baseUrl, SegmentBase segmentBase) {
|
long revisionId, Format format, String baseUrl, SegmentBase segmentBase) {
|
||||||
return newInstance(
|
return newInstance(revisionId, format, baseUrl, segmentBase, /* inbandEventStreams= */ null);
|
||||||
revisionId,
|
|
||||||
format,
|
|
||||||
baseUrl,
|
|
||||||
segmentBase,
|
|
||||||
/* inbandEventStreams= */ null,
|
|
||||||
/* periodStartUnixTimeMs= */ C.TIME_UNSET,
|
|
||||||
/* timeShiftBufferDepthMs= */ C.TIME_UNSET);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -91,9 +82,6 @@ public abstract class Representation {
|
||||||
* @param baseUrl The base URL.
|
* @param baseUrl The base URL.
|
||||||
* @param segmentBase A segment base element for 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 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.
|
* @return The constructed instance.
|
||||||
*/
|
*/
|
||||||
public static Representation newInstance(
|
public static Representation newInstance(
|
||||||
|
|
@ -101,17 +89,13 @@ public abstract class Representation {
|
||||||
Format format,
|
Format format,
|
||||||
String baseUrl,
|
String baseUrl,
|
||||||
SegmentBase segmentBase,
|
SegmentBase segmentBase,
|
||||||
@Nullable List<Descriptor> inbandEventStreams,
|
@Nullable List<Descriptor> inbandEventStreams) {
|
||||||
long periodStartUnixTimeMs,
|
|
||||||
long timeShiftBufferDepthMs) {
|
|
||||||
return newInstance(
|
return newInstance(
|
||||||
revisionId,
|
revisionId,
|
||||||
format,
|
format,
|
||||||
baseUrl,
|
baseUrl,
|
||||||
segmentBase,
|
segmentBase,
|
||||||
inbandEventStreams,
|
inbandEventStreams,
|
||||||
periodStartUnixTimeMs,
|
|
||||||
timeShiftBufferDepthMs,
|
|
||||||
/* cacheKey= */ null);
|
/* cacheKey= */ null);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -123,9 +107,6 @@ public abstract class Representation {
|
||||||
* @param baseUrl The base URL of the representation.
|
* @param baseUrl The base URL of the representation.
|
||||||
* @param segmentBase A segment base element for 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 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
|
* @param cacheKey An optional key to be returned from {@link #getCacheKey()}, or null. This
|
||||||
* parameter is ignored if {@code segmentBase} consists of multiple segments.
|
* parameter is ignored if {@code segmentBase} consists of multiple segments.
|
||||||
* @return The constructed instance.
|
* @return The constructed instance.
|
||||||
|
|
@ -136,8 +117,6 @@ public abstract class Representation {
|
||||||
String baseUrl,
|
String baseUrl,
|
||||||
SegmentBase segmentBase,
|
SegmentBase segmentBase,
|
||||||
@Nullable List<Descriptor> inbandEventStreams,
|
@Nullable List<Descriptor> inbandEventStreams,
|
||||||
long periodStartUnixTimeMs,
|
|
||||||
long timeShiftBufferDepthMs,
|
|
||||||
@Nullable String cacheKey) {
|
@Nullable String cacheKey) {
|
||||||
if (segmentBase instanceof SingleSegmentBase) {
|
if (segmentBase instanceof SingleSegmentBase) {
|
||||||
return new SingleSegmentRepresentation(
|
return new SingleSegmentRepresentation(
|
||||||
|
|
@ -150,13 +129,7 @@ public abstract class Representation {
|
||||||
C.LENGTH_UNSET);
|
C.LENGTH_UNSET);
|
||||||
} else if (segmentBase instanceof MultiSegmentBase) {
|
} else if (segmentBase instanceof MultiSegmentBase) {
|
||||||
return new MultiSegmentRepresentation(
|
return new MultiSegmentRepresentation(
|
||||||
revisionId,
|
revisionId, format, baseUrl, (MultiSegmentBase) segmentBase, inbandEventStreams);
|
||||||
format,
|
|
||||||
baseUrl,
|
|
||||||
(MultiSegmentBase) segmentBase,
|
|
||||||
inbandEventStreams,
|
|
||||||
periodStartUnixTimeMs,
|
|
||||||
timeShiftBufferDepthMs);
|
|
||||||
} else {
|
} else {
|
||||||
throw new IllegalArgumentException("segmentBase must be of type SingleSegmentBase or "
|
throw new IllegalArgumentException("segmentBase must be of type SingleSegmentBase or "
|
||||||
+ "MultiSegmentBase");
|
+ "MultiSegmentBase");
|
||||||
|
|
@ -309,8 +282,6 @@ public abstract class Representation {
|
||||||
implements DashSegmentIndex {
|
implements DashSegmentIndex {
|
||||||
|
|
||||||
@VisibleForTesting /* package */ final MultiSegmentBase segmentBase;
|
@VisibleForTesting /* package */ final MultiSegmentBase segmentBase;
|
||||||
private final long periodStartUnixTimeUs;
|
|
||||||
private final long timeShiftBufferDepthUs;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Creates the multi-segment Representation.
|
* Creates the multi-segment Representation.
|
||||||
|
|
@ -320,22 +291,15 @@ public abstract class Representation {
|
||||||
* @param baseUrl The base URL of the representation.
|
* @param baseUrl The base URL of the representation.
|
||||||
* @param segmentBase The segment base underlying the representation.
|
* @param segmentBase The segment base underlying the representation.
|
||||||
* @param inbandEventStreams The in-band event streams in the representation. May be null.
|
* @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(
|
public MultiSegmentRepresentation(
|
||||||
long revisionId,
|
long revisionId,
|
||||||
Format format,
|
Format format,
|
||||||
String baseUrl,
|
String baseUrl,
|
||||||
MultiSegmentBase segmentBase,
|
MultiSegmentBase segmentBase,
|
||||||
@Nullable List<Descriptor> inbandEventStreams,
|
@Nullable List<Descriptor> inbandEventStreams) {
|
||||||
long periodStartUnixTimeMs,
|
|
||||||
long timeShiftBufferDepthMs) {
|
|
||||||
super(revisionId, format, baseUrl, segmentBase, inbandEventStreams);
|
super(revisionId, format, baseUrl, segmentBase, inbandEventStreams);
|
||||||
this.segmentBase = segmentBase;
|
this.segmentBase = segmentBase;
|
||||||
this.periodStartUnixTimeUs = C.msToUs(periodStartUnixTimeMs);
|
|
||||||
this.timeShiftBufferDepthUs = C.msToUs(timeShiftBufferDepthMs);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|
@ -384,17 +348,7 @@ public abstract class Representation {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public long getFirstAvailableSegmentNum(long periodDurationUs, long nowUnixTimeUs) {
|
public long getFirstAvailableSegmentNum(long periodDurationUs, long nowUnixTimeUs) {
|
||||||
long segmentCount = segmentBase.getSegmentCount(periodDurationUs);
|
return segmentBase.getFirstAvailableSegmentNum(periodDurationUs, nowUnixTimeUs);
|
||||||
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
|
@Override
|
||||||
|
|
@ -404,18 +358,12 @@ public abstract class Representation {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public int getAvailableSegmentCount(long periodDurationUs, long nowUnixTimeUs) {
|
public int getAvailableSegmentCount(long periodDurationUs, long nowUnixTimeUs) {
|
||||||
int segmentCount = segmentBase.getSegmentCount(periodDurationUs);
|
return segmentBase.getAvailableSegmentCount(periodDurationUs, nowUnixTimeUs);
|
||||||
if (segmentCount != INDEX_UNBOUNDED) {
|
}
|
||||||
return segmentCount;
|
|
||||||
}
|
@Override
|
||||||
// The index is itself unbounded. We need to use the current time to calculate the range of
|
public long getNextSegmentAvailableTimeUs(long periodDurationUs, long nowUnixTimeUs) {
|
||||||
// available segments.
|
return segmentBase.getNextSegmentAvailableTimeUs(periodDurationUs, nowUnixTimeUs);
|
||||||
long liveEdgeTimeInPeriodUs = nowUnixTimeUs - periodStartUnixTimeUs;
|
|
||||||
long availabilityEndTimeUs = liveEdgeTimeInPeriodUs + segmentBase.availabilityTimeOffsetUs;
|
|
||||||
// getSegmentNum(availabilityEndTimeUs) will not be completed yet.
|
|
||||||
long firstIncompleteSegmentNum = getSegmentNum(availabilityEndTimeUs, periodDurationUs);
|
|
||||||
long firstAvailableSegmentNum = getFirstAvailableSegmentNum(periodDurationUs, nowUnixTimeUs);
|
|
||||||
return (int) (firstIncompleteSegmentNum - firstAvailableSegmentNum);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|
|
||||||
|
|
@ -15,9 +15,12 @@
|
||||||
*/
|
*/
|
||||||
package com.google.android.exoplayer2.source.dash.manifest;
|
package com.google.android.exoplayer2.source.dash.manifest;
|
||||||
|
|
||||||
|
import static com.google.android.exoplayer2.source.dash.DashSegmentIndex.INDEX_UNBOUNDED;
|
||||||
|
import static java.lang.Math.max;
|
||||||
import static java.lang.Math.min;
|
import static java.lang.Math.min;
|
||||||
|
|
||||||
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.source.dash.DashSegmentIndex;
|
import com.google.android.exoplayer2.source.dash.DashSegmentIndex;
|
||||||
import com.google.android.exoplayer2.util.Util;
|
import com.google.android.exoplayer2.util.Util;
|
||||||
|
|
@ -119,6 +122,8 @@ public abstract class SegmentBase {
|
||||||
/* package */ final long startNumber;
|
/* package */ final long startNumber;
|
||||||
/* package */ final long duration;
|
/* package */ final long duration;
|
||||||
@Nullable /* package */ final List<SegmentTimelineElement> segmentTimeline;
|
@Nullable /* package */ final List<SegmentTimelineElement> segmentTimeline;
|
||||||
|
private final long timeShiftBufferDepthUs;
|
||||||
|
private final long periodStartUnixTimeUs;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Offset to the current realtime at which segments become available, in microseconds, or {@link
|
* Offset to the current realtime at which segments become available, in microseconds, or {@link
|
||||||
|
|
@ -127,7 +132,7 @@ public abstract class SegmentBase {
|
||||||
* <p>Segments will be available once their end time ≤ currentRealTime +
|
* <p>Segments will be available once their end time ≤ currentRealTime +
|
||||||
* availabilityTimeOffset.
|
* availabilityTimeOffset.
|
||||||
*/
|
*/
|
||||||
/* package */ final long availabilityTimeOffsetUs;
|
@VisibleForTesting /* 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
|
||||||
|
|
@ -144,6 +149,9 @@ public abstract class SegmentBase {
|
||||||
* parameter.
|
* parameter.
|
||||||
* @param availabilityTimeOffsetUs The offset to the current realtime at which segments become
|
* @param availabilityTimeOffsetUs The offset to the current realtime at which segments become
|
||||||
* available in microseconds, or {@link C#TIME_UNSET} if not applicable.
|
* available in microseconds, or {@link C#TIME_UNSET} if not applicable.
|
||||||
|
* @param timeShiftBufferDepthUs The time shift buffer depth in microseconds.
|
||||||
|
* @param periodStartUnixTimeUs The start of the enclosing period in microseconds since the Unix
|
||||||
|
* epoch.
|
||||||
*/
|
*/
|
||||||
public MultiSegmentBase(
|
public MultiSegmentBase(
|
||||||
@Nullable RangedUri initialization,
|
@Nullable RangedUri initialization,
|
||||||
|
|
@ -152,15 +160,19 @@ public abstract class SegmentBase {
|
||||||
long startNumber,
|
long startNumber,
|
||||||
long duration,
|
long duration,
|
||||||
@Nullable List<SegmentTimelineElement> segmentTimeline,
|
@Nullable List<SegmentTimelineElement> segmentTimeline,
|
||||||
long availabilityTimeOffsetUs) {
|
long availabilityTimeOffsetUs,
|
||||||
|
long timeShiftBufferDepthUs,
|
||||||
|
long periodStartUnixTimeUs) {
|
||||||
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;
|
this.availabilityTimeOffsetUs = availabilityTimeOffsetUs;
|
||||||
|
this.timeShiftBufferDepthUs = timeShiftBufferDepthUs;
|
||||||
|
this.periodStartUnixTimeUs = periodStartUnixTimeUs;
|
||||||
}
|
}
|
||||||
|
|
||||||
/** @see DashSegmentIndex#getSegmentNum(long, long) */
|
/** See {@link DashSegmentIndex#getSegmentNum(long, long)}. */
|
||||||
public long getSegmentNum(long timeUs, long periodDurationUs) {
|
public long getSegmentNum(long timeUs, long periodDurationUs) {
|
||||||
final long firstSegmentNum = getFirstSegmentNum();
|
final long firstSegmentNum = getFirstSegmentNum();
|
||||||
final long segmentCount = getSegmentCount(periodDurationUs);
|
final long segmentCount = getSegmentCount(periodDurationUs);
|
||||||
|
|
@ -174,7 +186,7 @@ public abstract class SegmentBase {
|
||||||
// Ensure we stay within bounds.
|
// Ensure we stay within bounds.
|
||||||
return segmentNum < firstSegmentNum
|
return segmentNum < firstSegmentNum
|
||||||
? firstSegmentNum
|
? firstSegmentNum
|
||||||
: segmentCount == DashSegmentIndex.INDEX_UNBOUNDED
|
: segmentCount == INDEX_UNBOUNDED
|
||||||
? segmentNum
|
? segmentNum
|
||||||
: min(segmentNum, firstSegmentNum + segmentCount - 1);
|
: min(segmentNum, firstSegmentNum + segmentCount - 1);
|
||||||
} else {
|
} else {
|
||||||
|
|
@ -196,21 +208,21 @@ public abstract class SegmentBase {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/** @see DashSegmentIndex#getDurationUs(long, long) */
|
/** See {@link DashSegmentIndex#getDurationUs(long, long)}. */
|
||||||
public final long getSegmentDurationUs(long sequenceNumber, long periodDurationUs) {
|
public final long getSegmentDurationUs(long sequenceNumber, long periodDurationUs) {
|
||||||
if (segmentTimeline != null) {
|
if (segmentTimeline != null) {
|
||||||
long duration = segmentTimeline.get((int) (sequenceNumber - startNumber)).duration;
|
long duration = segmentTimeline.get((int) (sequenceNumber - startNumber)).duration;
|
||||||
return (duration * C.MICROS_PER_SECOND) / timescale;
|
return (duration * C.MICROS_PER_SECOND) / timescale;
|
||||||
} else {
|
} else {
|
||||||
int segmentCount = getSegmentCount(periodDurationUs);
|
int segmentCount = getSegmentCount(periodDurationUs);
|
||||||
return segmentCount != DashSegmentIndex.INDEX_UNBOUNDED
|
return segmentCount != INDEX_UNBOUNDED
|
||||||
&& sequenceNumber == (getFirstSegmentNum() + segmentCount - 1)
|
&& sequenceNumber == (getFirstSegmentNum() + segmentCount - 1)
|
||||||
? (periodDurationUs - getSegmentTimeUs(sequenceNumber))
|
? (periodDurationUs - getSegmentTimeUs(sequenceNumber))
|
||||||
: ((duration * C.MICROS_PER_SECOND) / timescale);
|
: ((duration * C.MICROS_PER_SECOND) / timescale);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/** @see DashSegmentIndex#getTimeUs(long) */
|
/** See {@link DashSegmentIndex#getTimeUs(long)}. */
|
||||||
public final long getSegmentTimeUs(long sequenceNumber) {
|
public final long getSegmentTimeUs(long sequenceNumber) {
|
||||||
long unscaledSegmentTime;
|
long unscaledSegmentTime;
|
||||||
if (segmentTimeline != null) {
|
if (segmentTimeline != null) {
|
||||||
|
|
@ -227,27 +239,66 @@ public abstract class SegmentBase {
|
||||||
* Returns a {@link RangedUri} defining the location of a segment for the given index in the
|
* Returns a {@link RangedUri} defining the location of a segment for the given index in the
|
||||||
* given representation.
|
* given representation.
|
||||||
*
|
*
|
||||||
* @see DashSegmentIndex#getSegmentUrl(long)
|
* <p>See {@link DashSegmentIndex#getSegmentUrl(long)}.
|
||||||
*/
|
*/
|
||||||
public abstract RangedUri getSegmentUrl(Representation representation, long index);
|
public abstract RangedUri getSegmentUrl(Representation representation, long index);
|
||||||
|
|
||||||
/** @see DashSegmentIndex#getFirstSegmentNum() */
|
/** See {@link DashSegmentIndex#getFirstSegmentNum()}. */
|
||||||
public long getFirstSegmentNum() {
|
public long getFirstSegmentNum() {
|
||||||
return startNumber;
|
return startNumber;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/** See {@link DashSegmentIndex#getFirstAvailableSegmentNum(long, long)}. */
|
||||||
* @see DashSegmentIndex#getSegmentCount(long)
|
public long getFirstAvailableSegmentNum(long periodDurationUs, long nowUnixTimeUs) {
|
||||||
*/
|
long segmentCount = getSegmentCount(periodDurationUs);
|
||||||
public abstract int getSegmentCount(long periodDurationUs);
|
if (segmentCount != INDEX_UNBOUNDED || timeShiftBufferDepthUs == C.TIME_UNSET) {
|
||||||
|
return 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);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/** See {@link DashSegmentIndex#getAvailableSegmentCount(long, long)}. */
|
||||||
* @see DashSegmentIndex#isExplicit()
|
public int getAvailableSegmentCount(long periodDurationUs, long nowUnixTimeUs) {
|
||||||
*/
|
int segmentCount = 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;
|
||||||
|
long availabilityTimeOffsetUs = liveEdgeTimeInPeriodUs + this.availabilityTimeOffsetUs;
|
||||||
|
// getSegmentNum(availabilityTimeOffsetUs) will not be completed yet.
|
||||||
|
long firstIncompleteSegmentNum = getSegmentNum(availabilityTimeOffsetUs, periodDurationUs);
|
||||||
|
long firstAvailableSegmentNum = getFirstAvailableSegmentNum(periodDurationUs, nowUnixTimeUs);
|
||||||
|
return (int) (firstIncompleteSegmentNum - firstAvailableSegmentNum);
|
||||||
|
}
|
||||||
|
|
||||||
|
/** See {@link DashSegmentIndex#getNextSegmentAvailableTimeUs(long, long)}. */
|
||||||
|
public long getNextSegmentAvailableTimeUs(long periodDurationUs, long nowUnixTimeUs) {
|
||||||
|
if (segmentTimeline != null) {
|
||||||
|
return C.TIME_UNSET;
|
||||||
|
}
|
||||||
|
long firstIncompleteSegmentNum =
|
||||||
|
getFirstAvailableSegmentNum(periodDurationUs, nowUnixTimeUs)
|
||||||
|
+ getAvailableSegmentCount(periodDurationUs, nowUnixTimeUs);
|
||||||
|
return getSegmentTimeUs(firstIncompleteSegmentNum)
|
||||||
|
+ getSegmentDurationUs(firstIncompleteSegmentNum, periodDurationUs)
|
||||||
|
- availabilityTimeOffsetUs;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** See {@link DashSegmentIndex#isExplicit()} */
|
||||||
public boolean isExplicit() {
|
public boolean isExplicit() {
|
||||||
return segmentTimeline != null;
|
return segmentTimeline != null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** See {@link DashSegmentIndex#getSegmentCount(long)}. */
|
||||||
|
public abstract int getSegmentCount(long periodDurationUs);
|
||||||
}
|
}
|
||||||
|
|
||||||
/** A {@link MultiSegmentBase} that uses a SegmentList to define its segments. */
|
/** A {@link MultiSegmentBase} that uses a SegmentList to define its segments. */
|
||||||
|
|
@ -271,6 +322,9 @@ public abstract class SegmentBase {
|
||||||
* @param availabilityTimeOffsetUs The offset to the current realtime at which segments become
|
* @param availabilityTimeOffsetUs The offset to the current realtime at which segments become
|
||||||
* available in microseconds, or {@link C#TIME_UNSET} if not applicable.
|
* 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.
|
||||||
|
* @param timeShiftBufferDepthUs The time shift buffer depth in microseconds.
|
||||||
|
* @param periodStartUnixTimeUs The start of the enclosing period in microseconds since the Unix
|
||||||
|
* epoch.
|
||||||
*/
|
*/
|
||||||
public SegmentList(
|
public SegmentList(
|
||||||
RangedUri initialization,
|
RangedUri initialization,
|
||||||
|
|
@ -280,7 +334,9 @@ public abstract class SegmentBase {
|
||||||
long duration,
|
long duration,
|
||||||
@Nullable List<SegmentTimelineElement> segmentTimeline,
|
@Nullable List<SegmentTimelineElement> segmentTimeline,
|
||||||
long availabilityTimeOffsetUs,
|
long availabilityTimeOffsetUs,
|
||||||
@Nullable List<RangedUri> mediaSegments) {
|
@Nullable List<RangedUri> mediaSegments,
|
||||||
|
long timeShiftBufferDepthUs,
|
||||||
|
long periodStartUnixTimeUs) {
|
||||||
super(
|
super(
|
||||||
initialization,
|
initialization,
|
||||||
timescale,
|
timescale,
|
||||||
|
|
@ -288,7 +344,9 @@ public abstract class SegmentBase {
|
||||||
startNumber,
|
startNumber,
|
||||||
duration,
|
duration,
|
||||||
segmentTimeline,
|
segmentTimeline,
|
||||||
availabilityTimeOffsetUs);
|
availabilityTimeOffsetUs,
|
||||||
|
timeShiftBufferDepthUs,
|
||||||
|
periodStartUnixTimeUs);
|
||||||
this.mediaSegments = mediaSegments;
|
this.mediaSegments = mediaSegments;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -339,6 +397,9 @@ public abstract class SegmentBase {
|
||||||
* 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.
|
||||||
* @param mediaTemplate A template defining the location of each media segment.
|
* @param mediaTemplate A template defining the location of each media segment.
|
||||||
|
* @param timeShiftBufferDepthUs The time shift buffer depth in microseconds.
|
||||||
|
* @param periodStartUnixTimeUs The start of the enclosing period in microseconds since the Unix
|
||||||
|
* epoch.
|
||||||
*/
|
*/
|
||||||
public SegmentTemplate(
|
public SegmentTemplate(
|
||||||
RangedUri initialization,
|
RangedUri initialization,
|
||||||
|
|
@ -350,7 +411,9 @@ public abstract class SegmentBase {
|
||||||
@Nullable List<SegmentTimelineElement> segmentTimeline,
|
@Nullable List<SegmentTimelineElement> segmentTimeline,
|
||||||
long availabilityTimeOffsetUs,
|
long availabilityTimeOffsetUs,
|
||||||
@Nullable UrlTemplate initializationTemplate,
|
@Nullable UrlTemplate initializationTemplate,
|
||||||
@Nullable UrlTemplate mediaTemplate) {
|
@Nullable UrlTemplate mediaTemplate,
|
||||||
|
long timeShiftBufferDepthUs,
|
||||||
|
long periodStartUnixTimeUs) {
|
||||||
super(
|
super(
|
||||||
initialization,
|
initialization,
|
||||||
timescale,
|
timescale,
|
||||||
|
|
@ -358,7 +421,9 @@ public abstract class SegmentBase {
|
||||||
startNumber,
|
startNumber,
|
||||||
duration,
|
duration,
|
||||||
segmentTimeline,
|
segmentTimeline,
|
||||||
availabilityTimeOffsetUs);
|
availabilityTimeOffsetUs,
|
||||||
|
timeShiftBufferDepthUs,
|
||||||
|
periodStartUnixTimeUs);
|
||||||
this.initializationTemplate = initializationTemplate;
|
this.initializationTemplate = initializationTemplate;
|
||||||
this.mediaTemplate = mediaTemplate;
|
this.mediaTemplate = mediaTemplate;
|
||||||
this.endNumber = endNumber;
|
this.endNumber = endNumber;
|
||||||
|
|
@ -399,7 +464,7 @@ public abstract class SegmentBase {
|
||||||
long durationUs = (duration * C.MICROS_PER_SECOND) / timescale;
|
long durationUs = (duration * C.MICROS_PER_SECOND) / timescale;
|
||||||
return (int) Util.ceilDivide(periodDurationUs, durationUs);
|
return (int) Util.ceilDivide(periodDurationUs, durationUs);
|
||||||
} else {
|
} else {
|
||||||
return DashSegmentIndex.INDEX_UNBOUNDED;
|
return INDEX_UNBOUNDED;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -15,6 +15,7 @@
|
||||||
*/
|
*/
|
||||||
package com.google.android.exoplayer2.source.dash.manifest;
|
package com.google.android.exoplayer2.source.dash.manifest;
|
||||||
|
|
||||||
|
import com.google.android.exoplayer2.C;
|
||||||
import com.google.android.exoplayer2.source.dash.DashSegmentIndex;
|
import com.google.android.exoplayer2.source.dash.DashSegmentIndex;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -71,6 +72,11 @@ import com.google.android.exoplayer2.source.dash.DashSegmentIndex;
|
||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public long getNextSegmentAvailableTimeUs(long periodDurationUs, long nowUnixTimeUs) {
|
||||||
|
return C.TIME_UNSET;
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean isExplicit() {
|
public boolean isExplicit() {
|
||||||
return true;
|
return true;
|
||||||
|
|
|
||||||
|
|
@ -19,16 +19,15 @@ import static com.google.common.truth.Truth.assertThat;
|
||||||
|
|
||||||
import androidx.test.ext.junit.runners.AndroidJUnit4;
|
import androidx.test.ext.junit.runners.AndroidJUnit4;
|
||||||
import com.google.android.exoplayer2.C;
|
import com.google.android.exoplayer2.C;
|
||||||
import com.google.android.exoplayer2.Format;
|
|
||||||
import org.junit.Test;
|
import org.junit.Test;
|
||||||
import org.junit.runner.RunWith;
|
import org.junit.runner.RunWith;
|
||||||
|
|
||||||
/** Unit test for {@link Representation}. */
|
/** Unit test for {@link SegmentBase}. */
|
||||||
@RunWith(AndroidJUnit4.class)
|
@RunWith(AndroidJUnit4.class)
|
||||||
public final class RepresentationTest {
|
public final class SegmentBaseTest {
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void getFirstAvailableSegmentNum_multiSegmentRepresentationWithUnboundedTemplate() {
|
public void getFirstAvailableSegmentNum_unboundedSegmentTemplate() {
|
||||||
long periodStartUnixTimeUs = 123_000_000_000_000L;
|
long periodStartUnixTimeUs = 123_000_000_000_000L;
|
||||||
SegmentBase.SegmentTemplate segmentTemplate =
|
SegmentBase.SegmentTemplate segmentTemplate =
|
||||||
new SegmentBase.SegmentTemplate(
|
new SegmentBase.SegmentTemplate(
|
||||||
|
|
@ -41,50 +40,43 @@ public final class RepresentationTest {
|
||||||
/* segmentTimeline= */ null,
|
/* segmentTimeline= */ null,
|
||||||
/* availabilityTimeOffsetUs= */ 500_000,
|
/* availabilityTimeOffsetUs= */ 500_000,
|
||||||
/* initializationTemplate= */ null,
|
/* initializationTemplate= */ null,
|
||||||
/* mediaTemplate= */ null);
|
/* mediaTemplate= */ null,
|
||||||
Representation.MultiSegmentRepresentation representation =
|
/* timeShiftBufferDepthUs= */ 6_000_000,
|
||||||
new Representation.MultiSegmentRepresentation(
|
/* periodStartUnixTimeUs= */ periodStartUnixTimeUs);
|
||||||
/* revisionId= */ 0,
|
|
||||||
new Format.Builder().build(),
|
|
||||||
/* baseUrl= */ "https://baseUrl/",
|
|
||||||
segmentTemplate,
|
|
||||||
/* inbandEventStreams= */ null,
|
|
||||||
/* periodStartUnixTimeMs= */ C.usToMs(periodStartUnixTimeUs),
|
|
||||||
/* timeShiftBufferDepthMs= */ 6_000);
|
|
||||||
|
|
||||||
assertThat(
|
assertThat(
|
||||||
representation.getFirstAvailableSegmentNum(
|
segmentTemplate.getFirstAvailableSegmentNum(
|
||||||
/* periodDurationUs= */ C.TIME_UNSET,
|
/* periodDurationUs= */ C.TIME_UNSET,
|
||||||
/* nowUnixTimeUs= */ periodStartUnixTimeUs - 10_000_000))
|
/* nowUnixTimeUs= */ periodStartUnixTimeUs - 10_000_000))
|
||||||
.isEqualTo(42);
|
.isEqualTo(42);
|
||||||
assertThat(
|
assertThat(
|
||||||
representation.getFirstAvailableSegmentNum(
|
segmentTemplate.getFirstAvailableSegmentNum(
|
||||||
/* periodDurationUs= */ C.TIME_UNSET, /* nowUnixTimeUs= */ periodStartUnixTimeUs))
|
/* periodDurationUs= */ C.TIME_UNSET, /* nowUnixTimeUs= */ periodStartUnixTimeUs))
|
||||||
.isEqualTo(42);
|
.isEqualTo(42);
|
||||||
assertThat(
|
assertThat(
|
||||||
representation.getFirstAvailableSegmentNum(
|
segmentTemplate.getFirstAvailableSegmentNum(
|
||||||
/* periodDurationUs= */ C.TIME_UNSET,
|
/* periodDurationUs= */ C.TIME_UNSET,
|
||||||
/* nowUnixTimeUs= */ periodStartUnixTimeUs + 7_999_999))
|
/* nowUnixTimeUs= */ periodStartUnixTimeUs + 7_999_999))
|
||||||
.isEqualTo(42);
|
.isEqualTo(42);
|
||||||
assertThat(
|
assertThat(
|
||||||
representation.getFirstAvailableSegmentNum(
|
segmentTemplate.getFirstAvailableSegmentNum(
|
||||||
/* periodDurationUs= */ C.TIME_UNSET,
|
/* periodDurationUs= */ C.TIME_UNSET,
|
||||||
/* nowUnixTimeUs= */ periodStartUnixTimeUs + 8_000_000))
|
/* nowUnixTimeUs= */ periodStartUnixTimeUs + 8_000_000))
|
||||||
.isEqualTo(43);
|
.isEqualTo(43);
|
||||||
assertThat(
|
assertThat(
|
||||||
representation.getFirstAvailableSegmentNum(
|
segmentTemplate.getFirstAvailableSegmentNum(
|
||||||
/* periodDurationUs= */ C.TIME_UNSET,
|
/* periodDurationUs= */ C.TIME_UNSET,
|
||||||
/* nowUnixTimeUs= */ periodStartUnixTimeUs + 9_999_999))
|
/* nowUnixTimeUs= */ periodStartUnixTimeUs + 9_999_999))
|
||||||
.isEqualTo(43);
|
.isEqualTo(43);
|
||||||
assertThat(
|
assertThat(
|
||||||
representation.getFirstAvailableSegmentNum(
|
segmentTemplate.getFirstAvailableSegmentNum(
|
||||||
/* periodDurationUs= */ C.TIME_UNSET,
|
/* periodDurationUs= */ C.TIME_UNSET,
|
||||||
/* nowUnixTimeUs= */ periodStartUnixTimeUs + 10_000_000))
|
/* nowUnixTimeUs= */ periodStartUnixTimeUs + 10_000_000))
|
||||||
.isEqualTo(44);
|
.isEqualTo(44);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void getAvailableSegmentCount_multiSegmentRepresentationWithUnboundedTemplate() {
|
public void getAvailableSegmentCount_unboundedSegmentTemplate() {
|
||||||
long periodStartUnixTimeUs = 123_000_000_000_000L;
|
long periodStartUnixTimeUs = 123_000_000_000_000L;
|
||||||
SegmentBase.SegmentTemplate segmentTemplate =
|
SegmentBase.SegmentTemplate segmentTemplate =
|
||||||
new SegmentBase.SegmentTemplate(
|
new SegmentBase.SegmentTemplate(
|
||||||
|
|
@ -97,55 +89,97 @@ public final class RepresentationTest {
|
||||||
/* segmentTimeline= */ null,
|
/* segmentTimeline= */ null,
|
||||||
/* availabilityTimeOffsetUs= */ 500_000,
|
/* availabilityTimeOffsetUs= */ 500_000,
|
||||||
/* initializationTemplate= */ null,
|
/* initializationTemplate= */ null,
|
||||||
/* mediaTemplate= */ null);
|
/* mediaTemplate= */ null,
|
||||||
Representation.MultiSegmentRepresentation representation =
|
/* timeShiftBufferDepthUs= */ 6_000_000,
|
||||||
new Representation.MultiSegmentRepresentation(
|
/* periodStartUnixTimeUs= */ periodStartUnixTimeUs);
|
||||||
/* revisionId= */ 0,
|
|
||||||
new Format.Builder().build(),
|
|
||||||
/* baseUrl= */ "https://baseUrl/",
|
|
||||||
segmentTemplate,
|
|
||||||
/* inbandEventStreams= */ null,
|
|
||||||
/* periodStartUnixTimeMs= */ C.usToMs(periodStartUnixTimeUs),
|
|
||||||
/* timeShiftBufferDepthMs= */ 6_000);
|
|
||||||
|
|
||||||
assertThat(
|
assertThat(
|
||||||
representation.getAvailableSegmentCount(
|
segmentTemplate.getAvailableSegmentCount(
|
||||||
/* periodDurationUs= */ C.TIME_UNSET,
|
/* periodDurationUs= */ C.TIME_UNSET,
|
||||||
/* nowUnixTimeUs= */ periodStartUnixTimeUs - 10_000_000))
|
/* nowUnixTimeUs= */ periodStartUnixTimeUs - 10_000_000))
|
||||||
.isEqualTo(0);
|
.isEqualTo(0);
|
||||||
assertThat(
|
assertThat(
|
||||||
representation.getAvailableSegmentCount(
|
segmentTemplate.getAvailableSegmentCount(
|
||||||
/* periodDurationUs= */ C.TIME_UNSET, /* nowUnixTimeUs= */ periodStartUnixTimeUs))
|
/* periodDurationUs= */ C.TIME_UNSET, /* nowUnixTimeUs= */ periodStartUnixTimeUs))
|
||||||
.isEqualTo(0);
|
.isEqualTo(0);
|
||||||
assertThat(
|
assertThat(
|
||||||
representation.getAvailableSegmentCount(
|
segmentTemplate.getAvailableSegmentCount(
|
||||||
/* periodDurationUs= */ C.TIME_UNSET,
|
/* periodDurationUs= */ C.TIME_UNSET,
|
||||||
/* nowUnixTimeUs= */ periodStartUnixTimeUs + 1_499_999))
|
/* nowUnixTimeUs= */ periodStartUnixTimeUs + 1_499_999))
|
||||||
.isEqualTo(0);
|
.isEqualTo(0);
|
||||||
assertThat(
|
assertThat(
|
||||||
representation.getAvailableSegmentCount(
|
segmentTemplate.getAvailableSegmentCount(
|
||||||
/* periodDurationUs= */ C.TIME_UNSET,
|
/* periodDurationUs= */ C.TIME_UNSET,
|
||||||
/* nowUnixTimeUs= */ periodStartUnixTimeUs + 1_500_000))
|
/* nowUnixTimeUs= */ periodStartUnixTimeUs + 1_500_000))
|
||||||
.isEqualTo(1);
|
.isEqualTo(1);
|
||||||
assertThat(
|
assertThat(
|
||||||
representation.getAvailableSegmentCount(
|
segmentTemplate.getAvailableSegmentCount(
|
||||||
/* periodDurationUs= */ C.TIME_UNSET,
|
/* periodDurationUs= */ C.TIME_UNSET,
|
||||||
/* nowUnixTimeUs= */ periodStartUnixTimeUs + 7_499_999))
|
/* nowUnixTimeUs= */ periodStartUnixTimeUs + 7_499_999))
|
||||||
.isEqualTo(3);
|
.isEqualTo(3);
|
||||||
assertThat(
|
assertThat(
|
||||||
representation.getAvailableSegmentCount(
|
segmentTemplate.getAvailableSegmentCount(
|
||||||
/* periodDurationUs= */ C.TIME_UNSET,
|
/* periodDurationUs= */ C.TIME_UNSET,
|
||||||
/* nowUnixTimeUs= */ periodStartUnixTimeUs + 7_500_000))
|
/* nowUnixTimeUs= */ periodStartUnixTimeUs + 7_500_000))
|
||||||
.isEqualTo(4);
|
.isEqualTo(4);
|
||||||
assertThat(
|
assertThat(
|
||||||
representation.getAvailableSegmentCount(
|
segmentTemplate.getAvailableSegmentCount(
|
||||||
/* periodDurationUs= */ C.TIME_UNSET,
|
/* periodDurationUs= */ C.TIME_UNSET,
|
||||||
/* nowUnixTimeUs= */ periodStartUnixTimeUs + 7_999_999))
|
/* nowUnixTimeUs= */ periodStartUnixTimeUs + 7_999_999))
|
||||||
.isEqualTo(4);
|
.isEqualTo(4);
|
||||||
assertThat(
|
assertThat(
|
||||||
representation.getAvailableSegmentCount(
|
segmentTemplate.getAvailableSegmentCount(
|
||||||
/* periodDurationUs= */ C.TIME_UNSET,
|
/* periodDurationUs= */ C.TIME_UNSET,
|
||||||
/* nowUnixTimeUs= */ periodStartUnixTimeUs + 8_000_000))
|
/* nowUnixTimeUs= */ periodStartUnixTimeUs + 8_000_000))
|
||||||
.isEqualTo(3);
|
.isEqualTo(3);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void getNextSegmentShiftTimeUse_unboundedSegmentTemplate() {
|
||||||
|
long periodStartUnixTimeUs = 123_000_000_000_000L;
|
||||||
|
SegmentBase.SegmentTemplate segmentTemplate =
|
||||||
|
new SegmentBase.SegmentTemplate(
|
||||||
|
/* initialization= */ null,
|
||||||
|
/* timescale= */ 1000,
|
||||||
|
/* presentationTimeOffset= */ 0,
|
||||||
|
/* startNumber= */ 42,
|
||||||
|
/* endNumber= */ C.INDEX_UNSET,
|
||||||
|
/* duration= */ 2000,
|
||||||
|
/* segmentTimeline= */ null,
|
||||||
|
/* availabilityTimeOffsetUs= */ 500_000,
|
||||||
|
/* initializationTemplate= */ null,
|
||||||
|
/* mediaTemplate= */ null,
|
||||||
|
/* timeShiftBufferDepthUs= */ 6_000_000,
|
||||||
|
/* periodStartUnixTimeUs= */ periodStartUnixTimeUs);
|
||||||
|
|
||||||
|
assertThat(
|
||||||
|
segmentTemplate.getNextSegmentAvailableTimeUs(
|
||||||
|
/* periodDurationUs= */ C.TIME_UNSET,
|
||||||
|
/* nowUnixTimeUs= */ periodStartUnixTimeUs - 10_000_000))
|
||||||
|
.isEqualTo(1_500_000);
|
||||||
|
assertThat(
|
||||||
|
segmentTemplate.getNextSegmentAvailableTimeUs(
|
||||||
|
/* periodDurationUs= */ C.TIME_UNSET, /* nowUnixTimeUs= */ periodStartUnixTimeUs))
|
||||||
|
.isEqualTo(1_500_000);
|
||||||
|
assertThat(
|
||||||
|
segmentTemplate.getNextSegmentAvailableTimeUs(
|
||||||
|
/* periodDurationUs= */ C.TIME_UNSET,
|
||||||
|
/* nowUnixTimeUs= */ periodStartUnixTimeUs + 1_499_999))
|
||||||
|
.isEqualTo(1_500_000);
|
||||||
|
assertThat(
|
||||||
|
segmentTemplate.getNextSegmentAvailableTimeUs(
|
||||||
|
/* periodDurationUs= */ C.TIME_UNSET,
|
||||||
|
/* nowUnixTimeUs= */ periodStartUnixTimeUs + 1_500_000))
|
||||||
|
.isEqualTo(3_500_000);
|
||||||
|
assertThat(
|
||||||
|
segmentTemplate.getNextSegmentAvailableTimeUs(
|
||||||
|
/* periodDurationUs= */ C.TIME_UNSET,
|
||||||
|
/* nowUnixTimeUs= */ periodStartUnixTimeUs + 17_499_999))
|
||||||
|
.isEqualTo(17_500_000);
|
||||||
|
assertThat(
|
||||||
|
segmentTemplate.getNextSegmentAvailableTimeUs(
|
||||||
|
/* periodDurationUs= */ C.TIME_UNSET,
|
||||||
|
/* nowUnixTimeUs= */ periodStartUnixTimeUs + 17_500_000))
|
||||||
|
.isEqualTo(19_500_000);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
Loading…
Reference in a new issue