Commit c107017a by bachinger Committed by kim-vde

Ensure implicit manifest updates arrives asap

PiperOrigin-RevId: 333714978
parent 397fe8f3
...@@ -50,6 +50,8 @@ import com.google.android.exoplayer2.source.dash.PlayerEmsgHandler.PlayerEmsgCal ...@@ -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; ...@@ -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 { ...@@ -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 { ...@@ -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 { ...@@ -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(
......
...@@ -102,6 +102,18 @@ public interface DashSegmentIndex { ...@@ -102,6 +102,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.
* *
* <p>If true is returned, each segment is defined explicitly by the index data, and all of the * <p>If true is returned, each segment is defined explicitly by the index data, and all of the
......
...@@ -15,6 +15,7 @@ ...@@ -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;
...@@ -57,6 +58,11 @@ public final class DashWrappingSegmentIndex implements DashSegmentIndex { ...@@ -57,6 +58,11 @@ public final class DashWrappingSegmentIndex implements DashSegmentIndex {
} }
@Override @Override
public long getNextSegmentAvailableTimeUs(long periodDurationUs, long nowUnixTimeUs) {
return C.TIME_UNSET;
}
@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 ...@@ -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 ...@@ -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 ...@@ -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 ...@@ -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 ...@@ -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 ...@@ -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 ...@@ -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 ...@@ -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 ...@@ -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 ...@@ -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 ...@@ -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 ...@@ -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 ...@@ -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 ...@@ -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 ...@@ -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 ...@@ -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 ...@@ -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 ...@@ -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 @@ ...@@ -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 { ...@@ -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 { ...@@ -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 { ...@@ -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 { ...@@ -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 { ...@@ -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 { ...@@ -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 { ...@@ -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 { ...@@ -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 { ...@@ -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 { ...@@ -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 @@ ...@@ -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 { ...@@ -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 { ...@@ -127,7 +132,7 @@ public abstract class SegmentBase {
* <p>Segments will be available once their end time &le; currentRealTime + * <p>Segments will be available once their end time &le; 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 { ...@@ -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 { ...@@ -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 { ...@@ -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 { ...@@ -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 { ...@@ -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 { ...@@ -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 { ...@@ -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 { ...@@ -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 { ...@@ -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 { ...@@ -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 { ...@@ -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 { ...@@ -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 @@ ...@@ -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;
/** /**
...@@ -72,6 +73,11 @@ import com.google.android.exoplayer2.source.dash.DashSegmentIndex; ...@@ -72,6 +73,11 @@ import com.google.android.exoplayer2.source.dash.DashSegmentIndex;
} }
@Override @Override
public long getNextSegmentAvailableTimeUs(long periodDurationUs, long nowUnixTimeUs) {
return C.TIME_UNSET;
}
@Override
public boolean isExplicit() { public boolean isExplicit() {
return true; return true;
} }
......
...@@ -19,16 +19,15 @@ import static com.google.common.truth.Truth.assertThat; ...@@ -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 { ...@@ -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 { ...@@ -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);
}
} }
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or sign in to comment