Ensure implicit manifest updates arrives asap

PiperOrigin-RevId: 333714978
This commit is contained in:
bachinger 2020-09-25 13:30:36 +01:00 committed by kim-vde
parent 397fe8f305
commit c107017a4b
8 changed files with 287 additions and 151 deletions

View file

@ -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.DashManifest;
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.upstream.Allocator;
import com.google.android.exoplayer2.upstream.DataSource;
@ -68,10 +70,12 @@ import com.google.android.exoplayer2.util.MimeTypes;
import com.google.android.exoplayer2.util.SntpClient;
import com.google.android.exoplayer2.util.Util;
import com.google.common.base.Charsets;
import com.google.common.math.LongMath;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.math.RoundingMode;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Collections;
@ -441,7 +445,7 @@ public final class DashMediaSource extends BaseMediaSource {
* MediaSourceCaller#onSourceInfoRefreshed(MediaSource, Timeline)} when the source's {@link
* 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.
*/
@ -1106,7 +1110,10 @@ public final class DashMediaSource extends BaseMediaSource {
handler.removeCallbacks(simulateManifestRefreshRunnable);
// If the window is changing implicitly, post a simulated manifest refresh to update it.
if (windowChangingImplicitly) {
handler.postDelayed(simulateManifestRefreshRunnable, NOTIFY_MANIFEST_INTERVAL_MS);
handler.postDelayed(
simulateManifestRefreshRunnable,
getIntervalUntilNextManifestRefreshMs(
manifest, Util.getNowUnixTimeMs(elapsedRealtimeOffsetMs)));
}
if (manifestLoadPending) {
startLoadingManifest();
@ -1165,6 +1172,38 @@ public final class DashMediaSource extends BaseMediaSource {
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 {
public static PeriodSeekInfo createPeriodSeekInfo(

View file

@ -101,6 +101,18 @@ public interface DashSegmentIndex {
*/
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.
*

View file

@ -15,6 +15,7 @@
*/
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.source.dash.manifest.RangedUri;
@ -56,6 +57,11 @@ public final class DashWrappingSegmentIndex implements DashSegmentIndex {
return chunkIndex.length;
}
@Override
public long getNextSegmentAvailableTimeUs(long periodDurationUs, long nowUnixTimeUs) {
return C.TIME_UNSET;
}
@Override
public long getTimeUs(long segmentNum) {
return chunkIndex.timesUs[(int) segmentNum] - timeOffsetUs;

View file

@ -291,9 +291,11 @@ public class DashManifestParser extends DefaultHandler
parseSegmentList(
xpp,
/* parent= */ null,
periodStartUnixTimeMs,
durationMs,
baseUrlAvailabilityTimeOffsetUs,
segmentBaseAvailabilityTimeOffsetUs);
segmentBaseAvailabilityTimeOffsetUs,
timeShiftBufferDepthMs);
} else if (XmlPullParserUtil.isStartTag(xpp, "SegmentTemplate")) {
segmentBaseAvailabilityTimeOffsetUs =
parseAvailabilityTimeOffsetUs(xpp, /* parentAvailabilityTimeOffsetUs= */ C.TIME_UNSET);
@ -302,9 +304,11 @@ public class DashManifestParser extends DefaultHandler
xpp,
/* parent= */ null,
ImmutableList.of(),
periodStartUnixTimeMs,
durationMs,
baseUrlAvailabilityTimeOffsetUs,
segmentBaseAvailabilityTimeOffsetUs);
segmentBaseAvailabilityTimeOffsetUs,
timeShiftBufferDepthMs);
} else if (XmlPullParserUtil.isStartTag(xpp, "AssetIdentifier")) {
assetIdentifier = parseDescriptor(xpp, "AssetIdentifier");
} else {
@ -407,9 +411,11 @@ public class DashManifestParser extends DefaultHandler
essentialProperties,
supplementalProperties,
segmentBase,
periodStartUnixTimeMs,
periodDurationMs,
baseUrlAvailabilityTimeOffsetUs,
segmentBaseAvailabilityTimeOffsetUs);
segmentBaseAvailabilityTimeOffsetUs,
timeShiftBufferDepthMs);
contentType =
checkContentTypeConsistency(
contentType, MimeTypes.getTrackType(representationInfo.format.sampleMimeType));
@ -423,9 +429,11 @@ public class DashManifestParser extends DefaultHandler
parseSegmentList(
xpp,
(SegmentList) segmentBase,
periodStartUnixTimeMs,
periodDurationMs,
baseUrlAvailabilityTimeOffsetUs,
segmentBaseAvailabilityTimeOffsetUs);
segmentBaseAvailabilityTimeOffsetUs,
timeShiftBufferDepthMs);
} else if (XmlPullParserUtil.isStartTag(xpp, "SegmentTemplate")) {
segmentBaseAvailabilityTimeOffsetUs =
parseAvailabilityTimeOffsetUs(xpp, segmentBaseAvailabilityTimeOffsetUs);
@ -434,9 +442,11 @@ public class DashManifestParser extends DefaultHandler
xpp,
(SegmentTemplate) segmentBase,
supplementalProperties,
periodStartUnixTimeMs,
periodDurationMs,
baseUrlAvailabilityTimeOffsetUs,
segmentBaseAvailabilityTimeOffsetUs);
segmentBaseAvailabilityTimeOffsetUs,
timeShiftBufferDepthMs);
} else if (XmlPullParserUtil.isStartTag(xpp, "InbandEventStream")) {
inbandEventStreams.add(parseDescriptor(xpp, "InbandEventStream"));
} else if (XmlPullParserUtil.isStartTag(xpp, "Label")) {
@ -455,9 +465,7 @@ public class DashManifestParser extends DefaultHandler
label,
drmSchemeType,
drmSchemeDatas,
inbandEventStreams,
periodStartUnixTimeMs,
timeShiftBufferDepthMs));
inbandEventStreams));
}
return buildAdaptationSet(
@ -599,9 +607,11 @@ public class DashManifestParser extends DefaultHandler
List<Descriptor> adaptationSetEssentialProperties,
List<Descriptor> adaptationSetSupplementalProperties,
@Nullable SegmentBase segmentBase,
long periodStartUnixTimeMs,
long periodDurationMs,
long baseUrlAvailabilityTimeOffsetUs,
long segmentBaseAvailabilityTimeOffsetUs)
long segmentBaseAvailabilityTimeOffsetUs,
long timeShiftBufferDepthMs)
throws XmlPullParserException, IOException {
String id = xpp.getAttributeValue(null, "id");
int bandwidth = parseInt(xpp, "bandwidth", Format.NO_VALUE);
@ -641,9 +651,11 @@ public class DashManifestParser extends DefaultHandler
parseSegmentList(
xpp,
(SegmentList) segmentBase,
periodStartUnixTimeMs,
periodDurationMs,
baseUrlAvailabilityTimeOffsetUs,
segmentBaseAvailabilityTimeOffsetUs);
segmentBaseAvailabilityTimeOffsetUs,
timeShiftBufferDepthMs);
} else if (XmlPullParserUtil.isStartTag(xpp, "SegmentTemplate")) {
segmentBaseAvailabilityTimeOffsetUs =
parseAvailabilityTimeOffsetUs(xpp, segmentBaseAvailabilityTimeOffsetUs);
@ -652,9 +664,11 @@ public class DashManifestParser extends DefaultHandler
xpp,
(SegmentTemplate) segmentBase,
adaptationSetSupplementalProperties,
periodStartUnixTimeMs,
periodDurationMs,
baseUrlAvailabilityTimeOffsetUs,
segmentBaseAvailabilityTimeOffsetUs);
segmentBaseAvailabilityTimeOffsetUs,
timeShiftBufferDepthMs);
} else if (XmlPullParserUtil.isStartTag(xpp, "ContentProtection")) {
Pair<String, SchemeData> contentProtection = parseContentProtection(xpp);
if (contentProtection.first != null) {
@ -754,9 +768,7 @@ public class DashManifestParser extends DefaultHandler
@Nullable String label,
@Nullable String extraDrmSchemeType,
ArrayList<SchemeData> extraDrmSchemeDatas,
ArrayList<Descriptor> extraInbandEventStreams,
long periodStartUnixTimeMs,
long timeShiftBufferDepthMs) {
ArrayList<Descriptor> extraInbandEventStreams) {
Format.Builder formatBuilder = representationInfo.format.buildUpon();
if (label != null) {
formatBuilder.setLabel(label);
@ -778,9 +790,7 @@ public class DashManifestParser extends DefaultHandler
formatBuilder.build(),
representationInfo.baseUrl,
representationInfo.segmentBase,
inbandEventStreams,
periodStartUnixTimeMs,
timeShiftBufferDepthMs);
inbandEventStreams);
}
// SegmentBase, SegmentList and SegmentTemplate parsing.
@ -825,9 +835,11 @@ public class DashManifestParser extends DefaultHandler
protected SegmentList parseSegmentList(
XmlPullParser xpp,
@Nullable SegmentList parent,
long periodStartUnixTimeMs,
long periodDurationMs,
long baseUrlAvailabilityTimeOffsetUs,
long segmentBaseAvailabilityTimeOffsetUs)
long segmentBaseAvailabilityTimeOffsetUs,
long timeShiftBufferDepthMs)
throws XmlPullParserException, IOException {
long timescale = parseLong(xpp, "timescale", parent != null ? parent.timescale : 1);
@ -873,7 +885,9 @@ public class DashManifestParser extends DefaultHandler
duration,
timeline,
availabilityTimeOffsetUs,
segments);
segments,
timeShiftBufferDepthMs,
periodStartUnixTimeMs);
}
protected SegmentList buildSegmentList(
@ -884,7 +898,9 @@ public class DashManifestParser extends DefaultHandler
long duration,
@Nullable List<SegmentTimelineElement> timeline,
long availabilityTimeOffsetUs,
@Nullable List<RangedUri> segments) {
@Nullable List<RangedUri> segments,
long timeShiftBufferDepthMs,
long periodStartUnixTimeMs) {
return new SegmentList(
initialization,
timescale,
@ -893,16 +909,20 @@ public class DashManifestParser extends DefaultHandler
duration,
timeline,
availabilityTimeOffsetUs,
segments);
segments,
C.msToUs(timeShiftBufferDepthMs),
C.msToUs(periodStartUnixTimeMs));
}
protected SegmentTemplate parseSegmentTemplate(
XmlPullParser xpp,
@Nullable SegmentTemplate parent,
List<Descriptor> adaptationSetSupplementalProperties,
long periodStartUnixTimeMs,
long periodDurationMs,
long baseUrlAvailabilityTimeOffsetUs,
long segmentBaseAvailabilityTimeOffsetUs)
long segmentBaseAvailabilityTimeOffsetUs,
long timeShiftBufferDepthMs)
throws XmlPullParserException, IOException {
long timescale = parseLong(xpp, "timescale", parent != null ? parent.timescale : 1);
long presentationTimeOffset = parseLong(xpp, "presentationTimeOffset",
@ -949,7 +969,9 @@ public class DashManifestParser extends DefaultHandler
timeline,
availabilityTimeOffsetUs,
initializationTemplate,
mediaTemplate);
mediaTemplate,
timeShiftBufferDepthMs,
periodStartUnixTimeMs);
}
protected SegmentTemplate buildSegmentTemplate(
@ -962,7 +984,9 @@ public class DashManifestParser extends DefaultHandler
List<SegmentTimelineElement> timeline,
long availabilityTimeOffsetUs,
@Nullable UrlTemplate initializationTemplate,
@Nullable UrlTemplate mediaTemplate) {
@Nullable UrlTemplate mediaTemplate,
long timeShiftBufferDepthMs,
long periodStartUnixTimeMs) {
return new SegmentTemplate(
initialization,
timescale,
@ -973,7 +997,9 @@ public class DashManifestParser extends DefaultHandler
timeline,
availabilityTimeOffsetUs,
initializationTemplate,
mediaTemplate);
mediaTemplate,
C.msToUs(timeShiftBufferDepthMs),
C.msToUs(periodStartUnixTimeMs));
}
/**

View file

@ -15,8 +15,6 @@
*/
package com.google.android.exoplayer2.source.dash.manifest;
import static java.lang.Math.max;
import android.net.Uri;
import androidx.annotation.Nullable;
import androidx.annotation.VisibleForTesting;
@ -73,14 +71,7 @@ public abstract class Representation {
*/
public static Representation newInstance(
long revisionId, Format format, String baseUrl, SegmentBase segmentBase) {
return newInstance(
revisionId,
format,
baseUrl,
segmentBase,
/* inbandEventStreams= */ null,
/* periodStartUnixTimeMs= */ C.TIME_UNSET,
/* timeShiftBufferDepthMs= */ C.TIME_UNSET);
return newInstance(revisionId, format, baseUrl, segmentBase, /* inbandEventStreams= */ null);
}
/**
@ -91,9 +82,6 @@ public abstract class Representation {
* @param baseUrl The base URL.
* @param segmentBase A segment base element for the representation.
* @param inbandEventStreams The in-band event streams in the representation. May be null.
* @param periodStartUnixTimeMs The start time of the enclosing {@link Period} in milliseconds
* since the Unix epoch, or {@link C#TIME_UNSET} is not applicable.
* @param timeShiftBufferDepthMs The {@link DashManifest#timeShiftBufferDepthMs}.
* @return The constructed instance.
*/
public static Representation newInstance(
@ -101,17 +89,13 @@ public abstract class Representation {
Format format,
String baseUrl,
SegmentBase segmentBase,
@Nullable List<Descriptor> inbandEventStreams,
long periodStartUnixTimeMs,
long timeShiftBufferDepthMs) {
@Nullable List<Descriptor> inbandEventStreams) {
return newInstance(
revisionId,
format,
baseUrl,
segmentBase,
inbandEventStreams,
periodStartUnixTimeMs,
timeShiftBufferDepthMs,
/* cacheKey= */ null);
}
@ -123,9 +107,6 @@ public abstract class Representation {
* @param baseUrl The base URL of the representation.
* @param segmentBase A segment base element for the representation.
* @param inbandEventStreams The in-band event streams in the representation. May be null.
* @param periodStartUnixTimeMs The start time of the enclosing {@link Period} in milliseconds
* since the Unix epoch, or {@link C#TIME_UNSET} is not applicable.
* @param timeShiftBufferDepthMs The {@link DashManifest#timeShiftBufferDepthMs}.
* @param cacheKey An optional key to be returned from {@link #getCacheKey()}, or null. This
* parameter is ignored if {@code segmentBase} consists of multiple segments.
* @return The constructed instance.
@ -136,8 +117,6 @@ public abstract class Representation {
String baseUrl,
SegmentBase segmentBase,
@Nullable List<Descriptor> inbandEventStreams,
long periodStartUnixTimeMs,
long timeShiftBufferDepthMs,
@Nullable String cacheKey) {
if (segmentBase instanceof SingleSegmentBase) {
return new SingleSegmentRepresentation(
@ -150,13 +129,7 @@ public abstract class Representation {
C.LENGTH_UNSET);
} else if (segmentBase instanceof MultiSegmentBase) {
return new MultiSegmentRepresentation(
revisionId,
format,
baseUrl,
(MultiSegmentBase) segmentBase,
inbandEventStreams,
periodStartUnixTimeMs,
timeShiftBufferDepthMs);
revisionId, format, baseUrl, (MultiSegmentBase) segmentBase, inbandEventStreams);
} else {
throw new IllegalArgumentException("segmentBase must be of type SingleSegmentBase or "
+ "MultiSegmentBase");
@ -309,8 +282,6 @@ public abstract class Representation {
implements DashSegmentIndex {
@VisibleForTesting /* package */ final MultiSegmentBase segmentBase;
private final long periodStartUnixTimeUs;
private final long timeShiftBufferDepthUs;
/**
* Creates the multi-segment Representation.
@ -320,22 +291,15 @@ public abstract class Representation {
* @param baseUrl The base URL of the representation.
* @param segmentBase The segment base underlying the representation.
* @param inbandEventStreams The in-band event streams in the representation. May be null.
* @param periodStartUnixTimeMs The start time of the enclosing {@link Period} in milliseconds
* since the Unix epoch, or {@link C#TIME_UNSET} is not applicable.
* @param timeShiftBufferDepthMs The {@link DashManifest#timeShiftBufferDepthMs}.
*/
public MultiSegmentRepresentation(
long revisionId,
Format format,
String baseUrl,
MultiSegmentBase segmentBase,
@Nullable List<Descriptor> inbandEventStreams,
long periodStartUnixTimeMs,
long timeShiftBufferDepthMs) {
@Nullable List<Descriptor> inbandEventStreams) {
super(revisionId, format, baseUrl, segmentBase, inbandEventStreams);
this.segmentBase = segmentBase;
this.periodStartUnixTimeUs = C.msToUs(periodStartUnixTimeMs);
this.timeShiftBufferDepthUs = C.msToUs(timeShiftBufferDepthMs);
}
@Override
@ -384,17 +348,7 @@ public abstract class Representation {
@Override
public long getFirstAvailableSegmentNum(long periodDurationUs, long nowUnixTimeUs) {
long segmentCount = segmentBase.getSegmentCount(periodDurationUs);
if (segmentCount != INDEX_UNBOUNDED || timeShiftBufferDepthUs == C.TIME_UNSET) {
return segmentBase.getFirstSegmentNum();
}
// The index is itself unbounded. We need to use the current time to calculate the range of
// available segments.
long liveEdgeTimeInPeriodUs = nowUnixTimeUs - periodStartUnixTimeUs;
long timeShiftBufferStartInPeriodUs = liveEdgeTimeInPeriodUs - timeShiftBufferDepthUs;
long timeShiftBufferStartSegmentNum =
getSegmentNum(timeShiftBufferStartInPeriodUs, periodDurationUs);
return max(getFirstSegmentNum(), timeShiftBufferStartSegmentNum);
return segmentBase.getFirstAvailableSegmentNum(periodDurationUs, nowUnixTimeUs);
}
@Override
@ -404,18 +358,12 @@ public abstract class Representation {
@Override
public int getAvailableSegmentCount(long periodDurationUs, long nowUnixTimeUs) {
int segmentCount = segmentBase.getSegmentCount(periodDurationUs);
if (segmentCount != INDEX_UNBOUNDED) {
return segmentCount;
}
// The index is itself unbounded. We need to use the current time to calculate the range of
// available segments.
long liveEdgeTimeInPeriodUs = nowUnixTimeUs - periodStartUnixTimeUs;
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);
return segmentBase.getAvailableSegmentCount(periodDurationUs, nowUnixTimeUs);
}
@Override
public long getNextSegmentAvailableTimeUs(long periodDurationUs, long nowUnixTimeUs) {
return segmentBase.getNextSegmentAvailableTimeUs(periodDurationUs, nowUnixTimeUs);
}
@Override

View file

@ -15,9 +15,12 @@
*/
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 androidx.annotation.Nullable;
import androidx.annotation.VisibleForTesting;
import com.google.android.exoplayer2.C;
import com.google.android.exoplayer2.source.dash.DashSegmentIndex;
import com.google.android.exoplayer2.util.Util;
@ -119,6 +122,8 @@ public abstract class SegmentBase {
/* package */ final long startNumber;
/* package */ final long duration;
@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
@ -127,7 +132,7 @@ public abstract class SegmentBase {
* <p>Segments will be available once their end time &le; currentRealTime +
* availabilityTimeOffset.
*/
/* package */ final long availabilityTimeOffsetUs;
@VisibleForTesting /* package */ final long availabilityTimeOffsetUs;
/**
* @param initialization A {@link RangedUri} corresponding to initialization data, if such data
@ -144,6 +149,9 @@ public abstract class SegmentBase {
* parameter.
* @param availabilityTimeOffsetUs The offset to the current realtime at which segments become
* available in microseconds, or {@link C#TIME_UNSET} if not applicable.
* @param timeShiftBufferDepthUs The time shift buffer depth in microseconds.
* @param periodStartUnixTimeUs The start of the enclosing period in microseconds since the Unix
* epoch.
*/
public MultiSegmentBase(
@Nullable RangedUri initialization,
@ -152,15 +160,19 @@ public abstract class SegmentBase {
long startNumber,
long duration,
@Nullable List<SegmentTimelineElement> segmentTimeline,
long availabilityTimeOffsetUs) {
long availabilityTimeOffsetUs,
long timeShiftBufferDepthUs,
long periodStartUnixTimeUs) {
super(initialization, timescale, presentationTimeOffset);
this.startNumber = startNumber;
this.duration = duration;
this.segmentTimeline = segmentTimeline;
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) {
final long firstSegmentNum = getFirstSegmentNum();
final long segmentCount = getSegmentCount(periodDurationUs);
@ -174,7 +186,7 @@ public abstract class SegmentBase {
// Ensure we stay within bounds.
return segmentNum < firstSegmentNum
? firstSegmentNum
: segmentCount == DashSegmentIndex.INDEX_UNBOUNDED
: segmentCount == INDEX_UNBOUNDED
? segmentNum
: min(segmentNum, firstSegmentNum + segmentCount - 1);
} else {
@ -196,21 +208,21 @@ public abstract class SegmentBase {
}
}
/** @see DashSegmentIndex#getDurationUs(long, long) */
/** See {@link DashSegmentIndex#getDurationUs(long, long)}. */
public final long getSegmentDurationUs(long sequenceNumber, long periodDurationUs) {
if (segmentTimeline != null) {
long duration = segmentTimeline.get((int) (sequenceNumber - startNumber)).duration;
return (duration * C.MICROS_PER_SECOND) / timescale;
} else {
int segmentCount = getSegmentCount(periodDurationUs);
return segmentCount != DashSegmentIndex.INDEX_UNBOUNDED
&& sequenceNumber == (getFirstSegmentNum() + segmentCount - 1)
return segmentCount != INDEX_UNBOUNDED
&& sequenceNumber == (getFirstSegmentNum() + segmentCount - 1)
? (periodDurationUs - getSegmentTimeUs(sequenceNumber))
: ((duration * C.MICROS_PER_SECOND) / timescale);
}
}
/** @see DashSegmentIndex#getTimeUs(long) */
/** See {@link DashSegmentIndex#getTimeUs(long)}. */
public final long getSegmentTimeUs(long sequenceNumber) {
long unscaledSegmentTime;
if (segmentTimeline != null) {
@ -227,27 +239,66 @@ public abstract class SegmentBase {
* Returns a {@link RangedUri} defining the location of a segment for the given index in the
* given representation.
*
* @see DashSegmentIndex#getSegmentUrl(long)
* <p>See {@link DashSegmentIndex#getSegmentUrl(long)}.
*/
public abstract RangedUri getSegmentUrl(Representation representation, long index);
/** @see DashSegmentIndex#getFirstSegmentNum() */
/** See {@link DashSegmentIndex#getFirstSegmentNum()}. */
public long getFirstSegmentNum() {
return startNumber;
}
/**
* @see DashSegmentIndex#getSegmentCount(long)
*/
public abstract int getSegmentCount(long periodDurationUs);
/** See {@link DashSegmentIndex#getFirstAvailableSegmentNum(long, long)}. */
public long getFirstAvailableSegmentNum(long periodDurationUs, long nowUnixTimeUs) {
long segmentCount = getSegmentCount(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 DashSegmentIndex#isExplicit()
*/
/** See {@link DashSegmentIndex#getAvailableSegmentCount(long, long)}. */
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() {
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. */
@ -271,6 +322,9 @@ public abstract class SegmentBase {
* @param availabilityTimeOffsetUs The offset to the current realtime at which segments become
* available in microseconds, or {@link C#TIME_UNSET} if not applicable.
* @param mediaSegments A list of {@link RangedUri}s indicating the locations of the segments.
* @param timeShiftBufferDepthUs The time shift buffer depth in microseconds.
* @param periodStartUnixTimeUs The start of the enclosing period in microseconds since the Unix
* epoch.
*/
public SegmentList(
RangedUri initialization,
@ -280,7 +334,9 @@ public abstract class SegmentBase {
long duration,
@Nullable List<SegmentTimelineElement> segmentTimeline,
long availabilityTimeOffsetUs,
@Nullable List<RangedUri> mediaSegments) {
@Nullable List<RangedUri> mediaSegments,
long timeShiftBufferDepthUs,
long periodStartUnixTimeUs) {
super(
initialization,
timescale,
@ -288,7 +344,9 @@ public abstract class SegmentBase {
startNumber,
duration,
segmentTimeline,
availabilityTimeOffsetUs);
availabilityTimeOffsetUs,
timeShiftBufferDepthUs,
periodStartUnixTimeUs);
this.mediaSegments = mediaSegments;
}
@ -339,6 +397,9 @@ public abstract class SegmentBase {
* such data exists. If non-null then the {@code initialization} parameter is ignored. If
* null then {@code initialization} will be used.
* @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(
RangedUri initialization,
@ -350,7 +411,9 @@ public abstract class SegmentBase {
@Nullable List<SegmentTimelineElement> segmentTimeline,
long availabilityTimeOffsetUs,
@Nullable UrlTemplate initializationTemplate,
@Nullable UrlTemplate mediaTemplate) {
@Nullable UrlTemplate mediaTemplate,
long timeShiftBufferDepthUs,
long periodStartUnixTimeUs) {
super(
initialization,
timescale,
@ -358,7 +421,9 @@ public abstract class SegmentBase {
startNumber,
duration,
segmentTimeline,
availabilityTimeOffsetUs);
availabilityTimeOffsetUs,
timeShiftBufferDepthUs,
periodStartUnixTimeUs);
this.initializationTemplate = initializationTemplate;
this.mediaTemplate = mediaTemplate;
this.endNumber = endNumber;
@ -399,7 +464,7 @@ public abstract class SegmentBase {
long durationUs = (duration * C.MICROS_PER_SECOND) / timescale;
return (int) Util.ceilDivide(periodDurationUs, durationUs);
} else {
return DashSegmentIndex.INDEX_UNBOUNDED;
return INDEX_UNBOUNDED;
}
}
}

View file

@ -15,6 +15,7 @@
*/
package com.google.android.exoplayer2.source.dash.manifest;
import com.google.android.exoplayer2.C;
import com.google.android.exoplayer2.source.dash.DashSegmentIndex;
/**
@ -71,6 +72,11 @@ import com.google.android.exoplayer2.source.dash.DashSegmentIndex;
return 1;
}
@Override
public long getNextSegmentAvailableTimeUs(long periodDurationUs, long nowUnixTimeUs) {
return C.TIME_UNSET;
}
@Override
public boolean isExplicit() {
return true;

View file

@ -19,16 +19,15 @@ import static com.google.common.truth.Truth.assertThat;
import androidx.test.ext.junit.runners.AndroidJUnit4;
import com.google.android.exoplayer2.C;
import com.google.android.exoplayer2.Format;
import org.junit.Test;
import org.junit.runner.RunWith;
/** Unit test for {@link Representation}. */
/** Unit test for {@link SegmentBase}. */
@RunWith(AndroidJUnit4.class)
public final class RepresentationTest {
public final class SegmentBaseTest {
@Test
public void getFirstAvailableSegmentNum_multiSegmentRepresentationWithUnboundedTemplate() {
public void getFirstAvailableSegmentNum_unboundedSegmentTemplate() {
long periodStartUnixTimeUs = 123_000_000_000_000L;
SegmentBase.SegmentTemplate segmentTemplate =
new SegmentBase.SegmentTemplate(
@ -41,50 +40,43 @@ public final class RepresentationTest {
/* segmentTimeline= */ null,
/* availabilityTimeOffsetUs= */ 500_000,
/* initializationTemplate= */ null,
/* mediaTemplate= */ null);
Representation.MultiSegmentRepresentation representation =
new Representation.MultiSegmentRepresentation(
/* revisionId= */ 0,
new Format.Builder().build(),
/* baseUrl= */ "https://baseUrl/",
segmentTemplate,
/* inbandEventStreams= */ null,
/* periodStartUnixTimeMs= */ C.usToMs(periodStartUnixTimeUs),
/* timeShiftBufferDepthMs= */ 6_000);
/* mediaTemplate= */ null,
/* timeShiftBufferDepthUs= */ 6_000_000,
/* periodStartUnixTimeUs= */ periodStartUnixTimeUs);
assertThat(
representation.getFirstAvailableSegmentNum(
segmentTemplate.getFirstAvailableSegmentNum(
/* periodDurationUs= */ C.TIME_UNSET,
/* nowUnixTimeUs= */ periodStartUnixTimeUs - 10_000_000))
.isEqualTo(42);
assertThat(
representation.getFirstAvailableSegmentNum(
segmentTemplate.getFirstAvailableSegmentNum(
/* periodDurationUs= */ C.TIME_UNSET, /* nowUnixTimeUs= */ periodStartUnixTimeUs))
.isEqualTo(42);
assertThat(
representation.getFirstAvailableSegmentNum(
segmentTemplate.getFirstAvailableSegmentNum(
/* periodDurationUs= */ C.TIME_UNSET,
/* nowUnixTimeUs= */ periodStartUnixTimeUs + 7_999_999))
.isEqualTo(42);
assertThat(
representation.getFirstAvailableSegmentNum(
segmentTemplate.getFirstAvailableSegmentNum(
/* periodDurationUs= */ C.TIME_UNSET,
/* nowUnixTimeUs= */ periodStartUnixTimeUs + 8_000_000))
.isEqualTo(43);
assertThat(
representation.getFirstAvailableSegmentNum(
segmentTemplate.getFirstAvailableSegmentNum(
/* periodDurationUs= */ C.TIME_UNSET,
/* nowUnixTimeUs= */ periodStartUnixTimeUs + 9_999_999))
.isEqualTo(43);
assertThat(
representation.getFirstAvailableSegmentNum(
segmentTemplate.getFirstAvailableSegmentNum(
/* periodDurationUs= */ C.TIME_UNSET,
/* nowUnixTimeUs= */ periodStartUnixTimeUs + 10_000_000))
.isEqualTo(44);
}
@Test
public void getAvailableSegmentCount_multiSegmentRepresentationWithUnboundedTemplate() {
public void getAvailableSegmentCount_unboundedSegmentTemplate() {
long periodStartUnixTimeUs = 123_000_000_000_000L;
SegmentBase.SegmentTemplate segmentTemplate =
new SegmentBase.SegmentTemplate(
@ -97,55 +89,97 @@ public final class RepresentationTest {
/* segmentTimeline= */ null,
/* availabilityTimeOffsetUs= */ 500_000,
/* initializationTemplate= */ null,
/* mediaTemplate= */ null);
Representation.MultiSegmentRepresentation representation =
new Representation.MultiSegmentRepresentation(
/* revisionId= */ 0,
new Format.Builder().build(),
/* baseUrl= */ "https://baseUrl/",
segmentTemplate,
/* inbandEventStreams= */ null,
/* periodStartUnixTimeMs= */ C.usToMs(periodStartUnixTimeUs),
/* timeShiftBufferDepthMs= */ 6_000);
/* mediaTemplate= */ null,
/* timeShiftBufferDepthUs= */ 6_000_000,
/* periodStartUnixTimeUs= */ periodStartUnixTimeUs);
assertThat(
representation.getAvailableSegmentCount(
segmentTemplate.getAvailableSegmentCount(
/* periodDurationUs= */ C.TIME_UNSET,
/* nowUnixTimeUs= */ periodStartUnixTimeUs - 10_000_000))
.isEqualTo(0);
assertThat(
representation.getAvailableSegmentCount(
segmentTemplate.getAvailableSegmentCount(
/* periodDurationUs= */ C.TIME_UNSET, /* nowUnixTimeUs= */ periodStartUnixTimeUs))
.isEqualTo(0);
assertThat(
representation.getAvailableSegmentCount(
segmentTemplate.getAvailableSegmentCount(
/* periodDurationUs= */ C.TIME_UNSET,
/* nowUnixTimeUs= */ periodStartUnixTimeUs + 1_499_999))
.isEqualTo(0);
assertThat(
representation.getAvailableSegmentCount(
segmentTemplate.getAvailableSegmentCount(
/* periodDurationUs= */ C.TIME_UNSET,
/* nowUnixTimeUs= */ periodStartUnixTimeUs + 1_500_000))
.isEqualTo(1);
assertThat(
representation.getAvailableSegmentCount(
segmentTemplate.getAvailableSegmentCount(
/* periodDurationUs= */ C.TIME_UNSET,
/* nowUnixTimeUs= */ periodStartUnixTimeUs + 7_499_999))
.isEqualTo(3);
assertThat(
representation.getAvailableSegmentCount(
segmentTemplate.getAvailableSegmentCount(
/* periodDurationUs= */ C.TIME_UNSET,
/* nowUnixTimeUs= */ periodStartUnixTimeUs + 7_500_000))
.isEqualTo(4);
assertThat(
representation.getAvailableSegmentCount(
segmentTemplate.getAvailableSegmentCount(
/* periodDurationUs= */ C.TIME_UNSET,
/* nowUnixTimeUs= */ periodStartUnixTimeUs + 7_999_999))
.isEqualTo(4);
assertThat(
representation.getAvailableSegmentCount(
segmentTemplate.getAvailableSegmentCount(
/* periodDurationUs= */ C.TIME_UNSET,
/* nowUnixTimeUs= */ periodStartUnixTimeUs + 8_000_000))
.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);
}
}