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