Commit 5019da3e by hoangtc Committed by Oliver Woodman

Update ABR logic in AdaptiveTrackSelection for live streaming case

In live streaming, if the playback position is very close to live edge,
the buffered duration will never reach minDurationForQualityIncreaseMs,
which prevents switching from ever happening. So we will provide the
durationToLiveEdgeUs to AdaptiveTrackSelection in live streaming case,
so it can handle this edge case.

GitHub: #3017

-------------
Created by MOE: https://github.com/google/moe
MOE_MIGRATED_REVID=168535969
parent 39dbb9a7
...@@ -40,6 +40,7 @@ public class AdaptiveTrackSelection extends BaseTrackSelection { ...@@ -40,6 +40,7 @@ public class AdaptiveTrackSelection extends BaseTrackSelection {
private final int maxDurationForQualityDecreaseMs; private final int maxDurationForQualityDecreaseMs;
private final int minDurationToRetainAfterDiscardMs; private final int minDurationToRetainAfterDiscardMs;
private final float bandwidthFraction; private final float bandwidthFraction;
private final float bufferedFractionToLiveEdgeForQualityIncrease;
/** /**
* @param bandwidthMeter Provides an estimate of the currently available bandwidth. * @param bandwidthMeter Provides an estimate of the currently available bandwidth.
...@@ -48,7 +49,9 @@ public class AdaptiveTrackSelection extends BaseTrackSelection { ...@@ -48,7 +49,9 @@ public class AdaptiveTrackSelection extends BaseTrackSelection {
this (bandwidthMeter, DEFAULT_MAX_INITIAL_BITRATE, this (bandwidthMeter, DEFAULT_MAX_INITIAL_BITRATE,
DEFAULT_MIN_DURATION_FOR_QUALITY_INCREASE_MS, DEFAULT_MIN_DURATION_FOR_QUALITY_INCREASE_MS,
DEFAULT_MAX_DURATION_FOR_QUALITY_DECREASE_MS, DEFAULT_MAX_DURATION_FOR_QUALITY_DECREASE_MS,
DEFAULT_MIN_DURATION_TO_RETAIN_AFTER_DISCARD_MS, DEFAULT_BANDWIDTH_FRACTION); DEFAULT_MIN_DURATION_TO_RETAIN_AFTER_DISCARD_MS,
DEFAULT_BANDWIDTH_FRACTION,
DEFAULT_BUFFERED_FRACTION_TO_LIVE_EDGE_FOR_QUALITY_INCREASE);
} }
/** /**
...@@ -70,19 +73,53 @@ public class AdaptiveTrackSelection extends BaseTrackSelection { ...@@ -70,19 +73,53 @@ public class AdaptiveTrackSelection extends BaseTrackSelection {
public Factory(BandwidthMeter bandwidthMeter, int maxInitialBitrate, public Factory(BandwidthMeter bandwidthMeter, int maxInitialBitrate,
int minDurationForQualityIncreaseMs, int maxDurationForQualityDecreaseMs, int minDurationForQualityIncreaseMs, int maxDurationForQualityDecreaseMs,
int minDurationToRetainAfterDiscardMs, float bandwidthFraction) { int minDurationToRetainAfterDiscardMs, float bandwidthFraction) {
this (bandwidthMeter, maxInitialBitrate, minDurationForQualityIncreaseMs,
maxDurationForQualityDecreaseMs, minDurationToRetainAfterDiscardMs,
bandwidthFraction, DEFAULT_BUFFERED_FRACTION_TO_LIVE_EDGE_FOR_QUALITY_INCREASE);
}
/**
* @param bandwidthMeter Provides an estimate of the currently available bandwidth.
* @param maxInitialBitrate The maximum bitrate in bits per second that should be assumed
* when a bandwidth estimate is unavailable.
* @param minDurationForQualityIncreaseMs The minimum duration of buffered data required for
* the selected track to switch to one of higher quality.
* @param maxDurationForQualityDecreaseMs The maximum duration of buffered data required for
* the selected track to switch to one of lower quality.
* @param minDurationToRetainAfterDiscardMs When switching to a track of significantly higher
* quality, the selection may indicate that media already buffered at the lower quality can
* be discarded to speed up the switch. This is the minimum duration of media that must be
* retained at the lower quality.
* @param bandwidthFraction The fraction of the available bandwidth that the selection should
* consider available for use. Setting to a value less than 1 is recommended to account
* for inaccuracies in the bandwidth estimator.
* @param bufferedFractionToLiveEdgeForQualityIncrease For live streaming, the fraction of
* the duration from current playback position to the live edge that has to be buffered
* before the selected track can be switched to one of higher quality. This parameter is
* only applied when the playback position is closer to the live edge than
* {@code minDurationForQualityIncreaseMs}, which would otherwise prevent switching to a
* higher quality from happening.
*/
public Factory(BandwidthMeter bandwidthMeter, int maxInitialBitrate,
int minDurationForQualityIncreaseMs, int maxDurationForQualityDecreaseMs,
int minDurationToRetainAfterDiscardMs, float bandwidthFraction,
float bufferedFractionToLiveEdgeForQualityIncrease) {
this.bandwidthMeter = bandwidthMeter; this.bandwidthMeter = bandwidthMeter;
this.maxInitialBitrate = maxInitialBitrate; this.maxInitialBitrate = maxInitialBitrate;
this.minDurationForQualityIncreaseMs = minDurationForQualityIncreaseMs; this.minDurationForQualityIncreaseMs = minDurationForQualityIncreaseMs;
this.maxDurationForQualityDecreaseMs = maxDurationForQualityDecreaseMs; this.maxDurationForQualityDecreaseMs = maxDurationForQualityDecreaseMs;
this.minDurationToRetainAfterDiscardMs = minDurationToRetainAfterDiscardMs; this.minDurationToRetainAfterDiscardMs = minDurationToRetainAfterDiscardMs;
this.bandwidthFraction = bandwidthFraction; this.bandwidthFraction = bandwidthFraction;
this.bufferedFractionToLiveEdgeForQualityIncrease =
bufferedFractionToLiveEdgeForQualityIncrease;
} }
@Override @Override
public AdaptiveTrackSelection createTrackSelection(TrackGroup group, int... tracks) { public AdaptiveTrackSelection createTrackSelection(TrackGroup group, int... tracks) {
return new AdaptiveTrackSelection(group, tracks, bandwidthMeter, maxInitialBitrate, return new AdaptiveTrackSelection(group, tracks, bandwidthMeter, maxInitialBitrate,
minDurationForQualityIncreaseMs, maxDurationForQualityDecreaseMs, minDurationForQualityIncreaseMs, maxDurationForQualityDecreaseMs,
minDurationToRetainAfterDiscardMs, bandwidthFraction); minDurationToRetainAfterDiscardMs, bandwidthFraction,
bufferedFractionToLiveEdgeForQualityIncrease);
} }
} }
...@@ -92,6 +129,7 @@ public class AdaptiveTrackSelection extends BaseTrackSelection { ...@@ -92,6 +129,7 @@ public class AdaptiveTrackSelection extends BaseTrackSelection {
public static final int DEFAULT_MAX_DURATION_FOR_QUALITY_DECREASE_MS = 25000; public static final int DEFAULT_MAX_DURATION_FOR_QUALITY_DECREASE_MS = 25000;
public static final int DEFAULT_MIN_DURATION_TO_RETAIN_AFTER_DISCARD_MS = 25000; public static final int DEFAULT_MIN_DURATION_TO_RETAIN_AFTER_DISCARD_MS = 25000;
public static final float DEFAULT_BANDWIDTH_FRACTION = 0.75f; public static final float DEFAULT_BANDWIDTH_FRACTION = 0.75f;
public static final float DEFAULT_BUFFERED_FRACTION_TO_LIVE_EDGE_FOR_QUALITY_INCREASE = 0.75f;
private final BandwidthMeter bandwidthMeter; private final BandwidthMeter bandwidthMeter;
private final int maxInitialBitrate; private final int maxInitialBitrate;
...@@ -99,6 +137,7 @@ public class AdaptiveTrackSelection extends BaseTrackSelection { ...@@ -99,6 +137,7 @@ public class AdaptiveTrackSelection extends BaseTrackSelection {
private final long maxDurationForQualityDecreaseUs; private final long maxDurationForQualityDecreaseUs;
private final long minDurationToRetainAfterDiscardUs; private final long minDurationToRetainAfterDiscardUs;
private final float bandwidthFraction; private final float bandwidthFraction;
private final float bufferedFractionToLiveEdgeForQualityIncrease;
private int selectedIndex; private int selectedIndex;
private int reason; private int reason;
...@@ -114,7 +153,9 @@ public class AdaptiveTrackSelection extends BaseTrackSelection { ...@@ -114,7 +153,9 @@ public class AdaptiveTrackSelection extends BaseTrackSelection {
this (group, tracks, bandwidthMeter, DEFAULT_MAX_INITIAL_BITRATE, this (group, tracks, bandwidthMeter, DEFAULT_MAX_INITIAL_BITRATE,
DEFAULT_MIN_DURATION_FOR_QUALITY_INCREASE_MS, DEFAULT_MIN_DURATION_FOR_QUALITY_INCREASE_MS,
DEFAULT_MAX_DURATION_FOR_QUALITY_DECREASE_MS, DEFAULT_MAX_DURATION_FOR_QUALITY_DECREASE_MS,
DEFAULT_MIN_DURATION_TO_RETAIN_AFTER_DISCARD_MS, DEFAULT_BANDWIDTH_FRACTION); DEFAULT_MIN_DURATION_TO_RETAIN_AFTER_DISCARD_MS,
DEFAULT_BANDWIDTH_FRACTION,
DEFAULT_BUFFERED_FRACTION_TO_LIVE_EDGE_FOR_QUALITY_INCREASE);
} }
/** /**
...@@ -135,11 +176,17 @@ public class AdaptiveTrackSelection extends BaseTrackSelection { ...@@ -135,11 +176,17 @@ public class AdaptiveTrackSelection extends BaseTrackSelection {
* @param bandwidthFraction The fraction of the available bandwidth that the selection should * @param bandwidthFraction The fraction of the available bandwidth that the selection should
* consider available for use. Setting to a value less than 1 is recommended to account * consider available for use. Setting to a value less than 1 is recommended to account
* for inaccuracies in the bandwidth estimator. * for inaccuracies in the bandwidth estimator.
* @param bufferedFractionToLiveEdgeForQualityIncrease For live streaming, the fraction of
* the duration from current playback position to the live edge that has to be buffered
* before the selected track can be switched to one of higher quality. This parameter is
* only applied when the playback position is closer to the live edge than
* {@code minDurationForQualityIncreaseMs}, which would otherwise prevent switching to a
* higher quality from happening.
*/ */
public AdaptiveTrackSelection(TrackGroup group, int[] tracks, BandwidthMeter bandwidthMeter, public AdaptiveTrackSelection(TrackGroup group, int[] tracks, BandwidthMeter bandwidthMeter,
int maxInitialBitrate, long minDurationForQualityIncreaseMs, int maxInitialBitrate, long minDurationForQualityIncreaseMs,
long maxDurationForQualityDecreaseMs, long minDurationToRetainAfterDiscardMs, long maxDurationForQualityDecreaseMs, long minDurationToRetainAfterDiscardMs,
float bandwidthFraction) { float bandwidthFraction, float bufferedFractionToLiveEdgeForQualityIncrease) {
super(group, tracks); super(group, tracks);
this.bandwidthMeter = bandwidthMeter; this.bandwidthMeter = bandwidthMeter;
this.maxInitialBitrate = maxInitialBitrate; this.maxInitialBitrate = maxInitialBitrate;
...@@ -147,12 +194,14 @@ public class AdaptiveTrackSelection extends BaseTrackSelection { ...@@ -147,12 +194,14 @@ public class AdaptiveTrackSelection extends BaseTrackSelection {
this.maxDurationForQualityDecreaseUs = maxDurationForQualityDecreaseMs * 1000L; this.maxDurationForQualityDecreaseUs = maxDurationForQualityDecreaseMs * 1000L;
this.minDurationToRetainAfterDiscardUs = minDurationToRetainAfterDiscardMs * 1000L; this.minDurationToRetainAfterDiscardUs = minDurationToRetainAfterDiscardMs * 1000L;
this.bandwidthFraction = bandwidthFraction; this.bandwidthFraction = bandwidthFraction;
this.bufferedFractionToLiveEdgeForQualityIncrease =
bufferedFractionToLiveEdgeForQualityIncrease;
selectedIndex = determineIdealSelectedIndex(Long.MIN_VALUE); selectedIndex = determineIdealSelectedIndex(Long.MIN_VALUE);
reason = C.SELECTION_REASON_INITIAL; reason = C.SELECTION_REASON_INITIAL;
} }
@Override @Override
public void updateSelectedTrack(long bufferedDurationUs) { public void updateSelectedTrack(long bufferedDurationUs, long availableDurationUs) {
long nowMs = SystemClock.elapsedRealtime(); long nowMs = SystemClock.elapsedRealtime();
// Stash the current selection, then make a new one. // Stash the current selection, then make a new one.
int currentSelectedIndex = selectedIndex; int currentSelectedIndex = selectedIndex;
...@@ -160,12 +209,13 @@ public class AdaptiveTrackSelection extends BaseTrackSelection { ...@@ -160,12 +209,13 @@ public class AdaptiveTrackSelection extends BaseTrackSelection {
if (selectedIndex == currentSelectedIndex) { if (selectedIndex == currentSelectedIndex) {
return; return;
} }
if (!isBlacklisted(currentSelectedIndex, nowMs)) { if (!isBlacklisted(currentSelectedIndex, nowMs)) {
// Revert back to the current selection if conditions are not suitable for switching. // Revert back to the current selection if conditions are not suitable for switching.
Format currentFormat = getFormat(currentSelectedIndex); Format currentFormat = getFormat(currentSelectedIndex);
Format selectedFormat = getFormat(selectedIndex); Format selectedFormat = getFormat(selectedIndex);
if (selectedFormat.bitrate > currentFormat.bitrate if (selectedFormat.bitrate > currentFormat.bitrate
&& bufferedDurationUs < minDurationForQualityIncreaseUs) { && bufferedDurationUs < minDurationForQualityIncreaseUs(availableDurationUs)) {
// The selected track is a higher quality, but we have insufficient buffer to safely switch // The selected track is a higher quality, but we have insufficient buffer to safely switch
// up. Defer switching up for now. // up. Defer switching up for now.
selectedIndex = currentSelectedIndex; selectedIndex = currentSelectedIndex;
...@@ -251,4 +301,12 @@ public class AdaptiveTrackSelection extends BaseTrackSelection { ...@@ -251,4 +301,12 @@ public class AdaptiveTrackSelection extends BaseTrackSelection {
return lowestBitrateNonBlacklistedIndex; return lowestBitrateNonBlacklistedIndex;
} }
private long minDurationForQualityIncreaseUs(long availableDurationUs) {
boolean isAvailableDurationTooShort = availableDurationUs != C.TIME_UNSET
&& availableDurationUs <= minDurationForQualityIncreaseUs;
return isAvailableDurationTooShort
? (long) (availableDurationUs * bufferedFractionToLiveEdgeForQualityIncrease)
: minDurationForQualityIncreaseUs;
}
} }
...@@ -78,7 +78,7 @@ public final class FixedTrackSelection extends BaseTrackSelection { ...@@ -78,7 +78,7 @@ public final class FixedTrackSelection extends BaseTrackSelection {
} }
@Override @Override
public void updateSelectedTrack(long bufferedDurationUs) { public void updateSelectedTrack(long bufferedDurationUs, long availableDurationUs) {
// Do nothing. // Do nothing.
} }
......
...@@ -88,7 +88,7 @@ public final class RandomTrackSelection extends BaseTrackSelection { ...@@ -88,7 +88,7 @@ public final class RandomTrackSelection extends BaseTrackSelection {
} }
@Override @Override
public void updateSelectedTrack(long bufferedDurationUs) { public void updateSelectedTrack(long bufferedDurationUs, long availableDurationUs) {
// Count the number of non-blacklisted formats. // Count the number of non-blacklisted formats.
long nowMs = SystemClock.elapsedRealtime(); long nowMs = SystemClock.elapsedRealtime();
int nonBlacklistedFormatCount = 0; int nonBlacklistedFormatCount = 0;
......
...@@ -26,7 +26,7 @@ import java.util.List; ...@@ -26,7 +26,7 @@ import java.util.List;
* {@link TrackGroup}, and a possibly varying individual selected track from the subset. * {@link TrackGroup}, and a possibly varying individual selected track from the subset.
* <p> * <p>
* Tracks belonging to the subset are exposed in decreasing bandwidth order. The individual selected * Tracks belonging to the subset are exposed in decreasing bandwidth order. The individual selected
* track may change as a result of calling {@link #updateSelectedTrack(long)}. * track may change as a result of calling {@link #updateSelectedTrack(long, long)}.
*/ */
public interface TrackSelection { public interface TrackSelection {
...@@ -126,8 +126,11 @@ public interface TrackSelection { ...@@ -126,8 +126,11 @@ public interface TrackSelection {
* Updates the selected track. * Updates the selected track.
* *
* @param bufferedDurationUs The duration of media currently buffered in microseconds. * @param bufferedDurationUs The duration of media currently buffered in microseconds.
* @param availableDurationUs The duration of media available for buffering from the current
* playback position, in microseconds, or {@link C#TIME_UNSET} if media can be buffered
* to the end of the current period.
*/ */
void updateSelectedTrack(long bufferedDurationUs); void updateSelectedTrack(long bufferedDurationUs, long availableDurationUs);
/** /**
* May be called periodically by sources that load media in discrete {@link MediaChunk}s and * May be called periodically by sources that load media in discrete {@link MediaChunk}s and
...@@ -148,10 +151,10 @@ public interface TrackSelection { ...@@ -148,10 +151,10 @@ public interface TrackSelection {
/** /**
* Attempts to blacklist the track at the specified index in the selection, making it ineligible * Attempts to blacklist the track at the specified index in the selection, making it ineligible
* for selection by calls to {@link #updateSelectedTrack(long)} for the specified period of time. * for selection by calls to {@link #updateSelectedTrack(long, long)} for the specified period of
* Blacklisting will fail if all other tracks are currently blacklisted. If blacklisting the * time. Blacklisting will fail if all other tracks are currently blacklisted. If blacklisting the
* currently selected track, note that it will remain selected until the next call to * currently selected track, note that it will remain selected until the next call to
* {@link #updateSelectedTrack(long)}. * {@link #updateSelectedTrack(long, long)}.
* *
* @param index The index of the track in the selection. * @param index The index of the track in the selection.
* @param blacklistDurationMs The duration of time for which the track should be blacklisted, in * @param blacklistDurationMs The duration of time for which the track should be blacklisted, in
......
...@@ -95,6 +95,7 @@ public class DefaultDashChunkSource implements DashChunkSource { ...@@ -95,6 +95,7 @@ public class DefaultDashChunkSource implements DashChunkSource {
private int periodIndex; private int periodIndex;
private IOException fatalError; private IOException fatalError;
private boolean missingLastSegment; private boolean missingLastSegment;
private long liveEdgeTimeUs;
/** /**
* @param manifestLoaderErrorThrower Throws errors affecting loading of manifests. * @param manifestLoaderErrorThrower Throws errors affecting loading of manifests.
...@@ -130,6 +131,7 @@ public class DefaultDashChunkSource implements DashChunkSource { ...@@ -130,6 +131,7 @@ public class DefaultDashChunkSource implements DashChunkSource {
this.maxSegmentsPerLoad = maxSegmentsPerLoad; this.maxSegmentsPerLoad = maxSegmentsPerLoad;
long periodDurationUs = manifest.getPeriodDurationUs(periodIndex); long periodDurationUs = manifest.getPeriodDurationUs(periodIndex);
liveEdgeTimeUs = C.TIME_UNSET;
List<Representation> representations = getRepresentations(); List<Representation> representations = getRepresentations();
representationHolders = new RepresentationHolder[trackSelection.length()]; representationHolders = new RepresentationHolder[trackSelection.length()];
for (int i = 0; i < representationHolders.length; i++) { for (int i = 0; i < representationHolders.length; i++) {
...@@ -179,7 +181,8 @@ public class DefaultDashChunkSource implements DashChunkSource { ...@@ -179,7 +181,8 @@ public class DefaultDashChunkSource implements DashChunkSource {
} }
long bufferedDurationUs = previous != null ? (previous.endTimeUs - playbackPositionUs) : 0; long bufferedDurationUs = previous != null ? (previous.endTimeUs - playbackPositionUs) : 0;
trackSelection.updateSelectedTrack(bufferedDurationUs); long timeToLiveEdgeUs = resolveTimeToLiveEdgeUs(playbackPositionUs, previous == null);
trackSelection.updateSelectedTrack(bufferedDurationUs, timeToLiveEdgeUs);
RepresentationHolder representationHolder = RepresentationHolder representationHolder =
representationHolders[trackSelection.getSelectedIndex()]; representationHolders[trackSelection.getSelectedIndex()];
...@@ -203,7 +206,6 @@ public class DefaultDashChunkSource implements DashChunkSource { ...@@ -203,7 +206,6 @@ public class DefaultDashChunkSource implements DashChunkSource {
} }
} }
long nowUs = getNowUnixTimeUs();
int availableSegmentCount = representationHolder.getSegmentCount(); int availableSegmentCount = representationHolder.getSegmentCount();
if (availableSegmentCount == 0) { if (availableSegmentCount == 0) {
// The index doesn't define any segments. // The index doesn't define any segments.
...@@ -216,21 +218,23 @@ public class DefaultDashChunkSource implements DashChunkSource { ...@@ -216,21 +218,23 @@ public class DefaultDashChunkSource implements DashChunkSource {
if (availableSegmentCount == DashSegmentIndex.INDEX_UNBOUNDED) { if (availableSegmentCount == DashSegmentIndex.INDEX_UNBOUNDED) {
// The index is itself unbounded. We need to use the current time to calculate the range of // The index is itself unbounded. We need to use the current time to calculate the range of
// available segments. // available segments.
long liveEdgeTimeUs = nowUs - manifest.availabilityStartTime * 1000; long liveEdgeTimeUs = getNowUnixTimeUs() - C.msToUs(manifest.availabilityStartTime);
long periodStartUs = manifest.getPeriod(periodIndex).startMs * 1000; long periodStartUs = C.msToUs(manifest.getPeriod(periodIndex).startMs);
long liveEdgeTimeInPeriodUs = liveEdgeTimeUs - periodStartUs; long liveEdgeTimeInPeriodUs = liveEdgeTimeUs - periodStartUs;
if (manifest.timeShiftBufferDepth != C.TIME_UNSET) { if (manifest.timeShiftBufferDepth != C.TIME_UNSET) {
long bufferDepthUs = manifest.timeShiftBufferDepth * 1000; long bufferDepthUs = C.msToUs(manifest.timeShiftBufferDepth);
firstAvailableSegmentNum = Math.max(firstAvailableSegmentNum, firstAvailableSegmentNum = Math.max(firstAvailableSegmentNum,
representationHolder.getSegmentNum(liveEdgeTimeInPeriodUs - bufferDepthUs)); representationHolder.getSegmentNum(liveEdgeTimeInPeriodUs - bufferDepthUs));
} }
// getSegmentNum(liveEdgeTimestampUs) will not be completed yet, so subtract one to get the // getSegmentNum(liveEdgeTimeInPeriodUs) will not be completed yet, so subtract one to get the
// index of the last completed segment. // index of the last completed segment.
lastAvailableSegmentNum = representationHolder.getSegmentNum(liveEdgeTimeInPeriodUs) - 1; lastAvailableSegmentNum = representationHolder.getSegmentNum(liveEdgeTimeInPeriodUs) - 1;
} else { } else {
lastAvailableSegmentNum = firstAvailableSegmentNum + availableSegmentCount - 1; lastAvailableSegmentNum = firstAvailableSegmentNum + availableSegmentCount - 1;
} }
updateLiveEdgeTimeUs(representationHolder, lastAvailableSegmentNum);
int segmentNum; int segmentNum;
if (previous == null) { if (previous == null) {
segmentNum = Util.constrainValue(representationHolder.getSegmentNum(playbackPositionUs), segmentNum = Util.constrainValue(representationHolder.getSegmentNum(playbackPositionUs),
...@@ -311,6 +315,19 @@ public class DefaultDashChunkSource implements DashChunkSource { ...@@ -311,6 +315,19 @@ public class DefaultDashChunkSource implements DashChunkSource {
return representations; return representations;
} }
private void updateLiveEdgeTimeUs(RepresentationHolder representationHolder,
int lastAvailableSegmentNum) {
if (manifest.dynamic) {
DashSegmentIndex segmentIndex = representationHolder.representation.getIndex();
long lastSegmentDurationUs = segmentIndex.getDurationUs(lastAvailableSegmentNum,
manifest.getPeriodDurationUs(periodIndex));
liveEdgeTimeUs = segmentIndex.getTimeUs(lastAvailableSegmentNum)
+ lastSegmentDurationUs;
} else {
liveEdgeTimeUs = C.TIME_UNSET;
}
}
private long getNowUnixTimeUs() { private long getNowUnixTimeUs() {
if (elapsedRealtimeOffsetMs != 0) { if (elapsedRealtimeOffsetMs != 0) {
return (SystemClock.elapsedRealtime() + elapsedRealtimeOffsetMs) * 1000; return (SystemClock.elapsedRealtime() + elapsedRealtimeOffsetMs) * 1000;
...@@ -375,6 +392,12 @@ public class DefaultDashChunkSource implements DashChunkSource { ...@@ -375,6 +392,12 @@ public class DefaultDashChunkSource implements DashChunkSource {
} }
} }
private long resolveTimeToLiveEdgeUs(long playbackPositionUs, boolean isAfterPositionReset) {
boolean resolveTimeToLiveEdgePossible = manifest.dynamic
&& !isAfterPositionReset && liveEdgeTimeUs != C.TIME_UNSET;
return resolveTimeToLiveEdgePossible ? liveEdgeTimeUs - playbackPositionUs : C.TIME_UNSET;
}
// Protected classes. // Protected classes.
/** /**
......
...@@ -103,6 +103,7 @@ import java.util.List; ...@@ -103,6 +103,7 @@ import java.util.List;
// the way in which HlsSampleStreamWrapper generates track groups. Use only index based methods // the way in which HlsSampleStreamWrapper generates track groups. Use only index based methods
// in TrackSelection to avoid unexpected behavior. // in TrackSelection to avoid unexpected behavior.
private TrackSelection trackSelection; private TrackSelection trackSelection;
private long liveEdgeTimeUs;
/** /**
* @param playlistTracker The {@link HlsPlaylistTracker} from which to obtain media playlists. * @param playlistTracker The {@link HlsPlaylistTracker} from which to obtain media playlists.
...@@ -122,6 +123,7 @@ import java.util.List; ...@@ -122,6 +123,7 @@ import java.util.List;
this.variants = variants; this.variants = variants;
this.timestampAdjusterProvider = timestampAdjusterProvider; this.timestampAdjusterProvider = timestampAdjusterProvider;
this.muxedCaptionFormats = muxedCaptionFormats; this.muxedCaptionFormats = muxedCaptionFormats;
liveEdgeTimeUs = C.TIME_UNSET;
Format[] variantFormats = new Format[variants.length]; Format[] variantFormats = new Format[variants.length];
int[] initialTrackSelection = new int[variants.length]; int[] initialTrackSelection = new int[variants.length];
for (int i = 0; i < variants.length; i++) { for (int i = 0; i < variants.length; i++) {
...@@ -214,7 +216,8 @@ import java.util.List; ...@@ -214,7 +216,8 @@ import java.util.List;
(independentSegments ? previous.endTimeUs : previous.startTimeUs) - playbackPositionUs); (independentSegments ? previous.endTimeUs : previous.startTimeUs) - playbackPositionUs);
// Select the variant. // Select the variant.
trackSelection.updateSelectedTrack(bufferedDurationUs); long timeToLiveEdgeUs = resolveTimeToLiveEdgeUs(playbackPositionUs, previous == null);
trackSelection.updateSelectedTrack(bufferedDurationUs, timeToLiveEdgeUs);
int selectedVariantIndex = trackSelection.getSelectedIndexInTrackGroup(); int selectedVariantIndex = trackSelection.getSelectedIndexInTrackGroup();
boolean switchingVariant = oldVariantIndex != selectedVariantIndex; boolean switchingVariant = oldVariantIndex != selectedVariantIndex;
...@@ -228,6 +231,8 @@ import java.util.List; ...@@ -228,6 +231,8 @@ import java.util.List;
HlsMediaPlaylist mediaPlaylist = playlistTracker.getPlaylistSnapshot(selectedUrl); HlsMediaPlaylist mediaPlaylist = playlistTracker.getPlaylistSnapshot(selectedUrl);
independentSegments = mediaPlaylist.hasIndependentSegmentsTag; independentSegments = mediaPlaylist.hasIndependentSegmentsTag;
updateLiveEdgeTimeUs(mediaPlaylist);
// Select the chunk. // Select the chunk.
int chunkMediaSequence; int chunkMediaSequence;
if (previous == null || switchingVariant) { if (previous == null || switchingVariant) {
...@@ -360,6 +365,16 @@ import java.util.List; ...@@ -360,6 +365,16 @@ import java.util.List;
// Private methods. // Private methods.
private long resolveTimeToLiveEdgeUs(long playbackPositionUs, boolean isAfterPositionReset) {
final boolean resolveTimeToLiveEdgePossible = !isAfterPositionReset
&& liveEdgeTimeUs != C.TIME_UNSET;
return resolveTimeToLiveEdgePossible ? liveEdgeTimeUs - playbackPositionUs : C.TIME_UNSET;
}
private void updateLiveEdgeTimeUs(HlsMediaPlaylist mediaPlaylist) {
liveEdgeTimeUs = mediaPlaylist.hasEndTag ? C.TIME_UNSET : mediaPlaylist.getEndTimeUs();
}
private EncryptionKeyChunk newEncryptionKeyChunk(Uri keyUri, String iv, int variantIndex, private EncryptionKeyChunk newEncryptionKeyChunk(Uri keyUri, String iv, int variantIndex,
int trackSelectionReason, Object trackSelectionData) { int trackSelectionReason, Object trackSelectionData) {
DataSpec dataSpec = new DataSpec(keyUri, 0, C.LENGTH_UNSET, null, DataSpec.FLAG_ALLOW_GZIP); DataSpec dataSpec = new DataSpec(keyUri, 0, C.LENGTH_UNSET, null, DataSpec.FLAG_ALLOW_GZIP);
...@@ -409,7 +424,7 @@ import java.util.List; ...@@ -409,7 +424,7 @@ import java.util.List;
} }
@Override @Override
public void updateSelectedTrack(long bufferedDurationUs) { public void updateSelectedTrack(long bufferedDurationUs, long availableDurationUs) {
long nowMs = SystemClock.elapsedRealtime(); long nowMs = SystemClock.elapsedRealtime();
if (!isBlacklisted(selectedIndex, nowMs)) { if (!isBlacklisted(selectedIndex, nowMs)) {
return; return;
......
...@@ -92,7 +92,6 @@ import java.util.LinkedList; ...@@ -92,7 +92,6 @@ import java.util.LinkedList;
private boolean prepared; private boolean prepared;
private int enabledTrackCount; private int enabledTrackCount;
private Format downstreamTrackFormat; private Format downstreamTrackFormat;
private int upstreamChunkUid;
private boolean released; private boolean released;
// Tracks are complicated in HLS. See documentation of buildTracks for details. // Tracks are complicated in HLS. See documentation of buildTracks for details.
...@@ -255,7 +254,7 @@ import java.util.LinkedList; ...@@ -255,7 +254,7 @@ import java.util.LinkedList;
// may need to be discarded. // may need to be discarded.
boolean primarySampleQueueDirty = false; boolean primarySampleQueueDirty = false;
if (!seenFirstTrackSelection) { if (!seenFirstTrackSelection) {
primaryTrackSelection.updateSelectedTrack(0); primaryTrackSelection.updateSelectedTrack(0, C.TIME_UNSET);
int chunkIndex = chunkSource.getTrackGroup().indexOf(mediaChunks.getLast().trackFormat); int chunkIndex = chunkSource.getTrackGroup().indexOf(mediaChunks.getLast().trackFormat);
if (primaryTrackSelection.getSelectedIndexInTrackGroup() != chunkIndex) { if (primaryTrackSelection.getSelectedIndexInTrackGroup() != chunkIndex) {
// This is the first selection and the chunk loaded during preparation does not match // This is the first selection and the chunk loaded during preparation does not match
...@@ -553,7 +552,6 @@ import java.util.LinkedList; ...@@ -553,7 +552,6 @@ import java.util.LinkedList;
* samples already queued to the wrapper. * samples already queued to the wrapper.
*/ */
public void init(int chunkUid, boolean shouldSpliceIn) { public void init(int chunkUid, boolean shouldSpliceIn) {
upstreamChunkUid = chunkUid;
for (SampleQueue sampleQueue : sampleQueues) { for (SampleQueue sampleQueue : sampleQueues) {
sampleQueue.sourceId(chunkUid); sampleQueue.sourceId(chunkUid);
} }
......
...@@ -155,7 +155,8 @@ public class DefaultSsChunkSource implements SsChunkSource { ...@@ -155,7 +155,8 @@ public class DefaultSsChunkSource implements SsChunkSource {
} }
long bufferedDurationUs = previous != null ? (previous.endTimeUs - playbackPositionUs) : 0; long bufferedDurationUs = previous != null ? (previous.endTimeUs - playbackPositionUs) : 0;
trackSelection.updateSelectedTrack(bufferedDurationUs); long timeToLiveEdgeUs = resolveTimeToLiveEdgeUs(playbackPositionUs);
trackSelection.updateSelectedTrack(bufferedDurationUs, timeToLiveEdgeUs);
StreamElement streamElement = manifest.streamElements[elementIndex]; StreamElement streamElement = manifest.streamElements[elementIndex];
if (streamElement.chunkCount == 0) { if (streamElement.chunkCount == 0) {
...@@ -222,4 +223,20 @@ public class DefaultSsChunkSource implements SsChunkSource { ...@@ -222,4 +223,20 @@ public class DefaultSsChunkSource implements SsChunkSource {
extractorWrapper); extractorWrapper);
} }
private long resolveTimeToLiveEdgeUs(long playbackPositionUs) {
if (!manifest.isLive) {
return C.TIME_UNSET;
}
StreamElement currentElement = manifest.streamElements[elementIndex];
if (currentElement.chunkCount == 0) {
return C.TIME_UNSET;
}
int lastChunkIndex = currentElement.chunkCount - 1;
long lastChunkEndTimeUs = currentElement.getStartTimeUs(lastChunkIndex)
+ currentElement.getChunkDurationUs(lastChunkIndex);
return lastChunkEndTimeUs - playbackPositionUs;
}
} }
...@@ -16,6 +16,7 @@ ...@@ -16,6 +16,7 @@
package com.google.android.exoplayer2.testutil; package com.google.android.exoplayer2.testutil;
import android.net.Uri; import android.net.Uri;
import com.google.android.exoplayer2.C;
import com.google.android.exoplayer2.Format; import com.google.android.exoplayer2.Format;
import com.google.android.exoplayer2.source.chunk.Chunk; import com.google.android.exoplayer2.source.chunk.Chunk;
import com.google.android.exoplayer2.source.chunk.ChunkHolder; import com.google.android.exoplayer2.source.chunk.ChunkHolder;
...@@ -83,7 +84,7 @@ public final class FakeChunkSource implements ChunkSource { ...@@ -83,7 +84,7 @@ public final class FakeChunkSource implements ChunkSource {
@Override @Override
public void getNextChunk(MediaChunk previous, long playbackPositionUs, ChunkHolder out) { public void getNextChunk(MediaChunk previous, long playbackPositionUs, ChunkHolder out) {
long bufferedDurationUs = previous != null ? (previous.endTimeUs - playbackPositionUs) : 0; long bufferedDurationUs = previous != null ? (previous.endTimeUs - playbackPositionUs) : 0;
trackSelection.updateSelectedTrack(bufferedDurationUs); trackSelection.updateSelectedTrack(bufferedDurationUs, C.TIME_UNSET);
int chunkIndex = previous == null ? dataSet.getChunkIndexByPosition(playbackPositionUs) int chunkIndex = previous == null ? dataSet.getChunkIndexByPosition(playbackPositionUs)
: previous.getNextChunkIndex(); : previous.getNextChunkIndex();
if (chunkIndex >= dataSet.getChunkCount()) { if (chunkIndex >= dataSet.getChunkCount()) {
......
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