Project default start pos to fix VOD->Live transitions

-------------
Created by MOE: https://github.com/google/moe
MOE_MIGRATED_REVID=138493584
This commit is contained in:
olly 2016-11-08 02:12:59 -08:00 committed by Oliver Woodman
parent 89ad5e6db3
commit 7b0effc2d0
6 changed files with 151 additions and 68 deletions

View File

@ -1014,6 +1014,15 @@ import java.io.IOException;
return getPeriodPosition(timeline, windowIndex, windowPositionUs);
}
/**
* Calls {@link #getPeriodPosition(Timeline, int, long, long)} with a zero default position
* projection.
*/
private Pair<Integer, Long> getPeriodPosition(Timeline timeline, int windowIndex,
long windowPositionUs) {
return getPeriodPosition(timeline, windowIndex, windowPositionUs, 0);
}
/**
* Converts (windowIndex, windowPositionUs) to the corresponding (periodIndex, periodPositionUs).
*
@ -1021,14 +1030,23 @@ import java.io.IOException;
* @param windowIndex The window index.
* @param windowPositionUs The window time, or {@link C#TIME_UNSET} to use the window's default
* start position.
* @return The corresponding (periodIndex, periodPositionUs).
* @param defaultPositionProjectionUs If {@code windowPositionUs} is {@link C#TIME_UNSET}, the
* duration into the future by which the window's position should be projected.
* @return The corresponding (periodIndex, periodPositionUs), or null if {@code #windowPositionUs}
* is {@link C#TIME_UNSET}, {@code defaultPositionProjectionUs} is non-zero, and the window's
* position could not be projected by {@code defaultPositionProjectionUs}.
*/
private Pair<Integer, Long> getPeriodPosition(Timeline timeline, int windowIndex,
long windowPositionUs) {
timeline.getWindow(windowIndex, window);
long windowPositionUs, long defaultPositionProjectionUs) {
timeline.getWindow(windowIndex, window, false, defaultPositionProjectionUs);
if (windowPositionUs == C.TIME_UNSET) {
windowPositionUs = window.getDefaultPositionUs();
if (windowPositionUs == C.TIME_UNSET) {
return null;
}
}
int periodIndex = window.firstPeriodIndex;
long periodPositionUs = window.getPositionInFirstPeriodUs()
+ (windowPositionUs == C.TIME_UNSET ? window.getDefaultPositionUs() : windowPositionUs);
long periodPositionUs = window.getPositionInFirstPeriodUs() + windowPositionUs;
long periodDurationUs = timeline.getPeriod(periodIndex, period).getDurationUs();
while (periodDurationUs != C.TIME_UNSET && periodPositionUs >= periodDurationUs
&& periodIndex < window.lastPeriodIndex) {
@ -1062,11 +1080,24 @@ import java.io.IOException;
: (isFirstPeriodInWindow ? C.TIME_UNSET : 0);
if (periodStartPositionUs == C.TIME_UNSET) {
// This is the first period of a new window or we don't have a start position, so seek to
// the default position for the window.
Pair<Integer, Long> defaultPosition = getPeriodPosition(windowIndex, C.TIME_UNSET);
// the default position for the window. If we're buffering ahead we also project the
// default position so that it's correct for starting playing the buffered duration of
// time in the future.
long defaultPositionProjectionUs = loadingPeriodHolder == null ? 0
: (loadingPeriodHolder.rendererPositionOffsetUs
+ timeline.getPeriod(loadingPeriodHolder.index, period).getDurationUs()
- loadingPeriodHolder.startPositionUs - rendererPositionUs);
Pair<Integer, Long> defaultPosition = getPeriodPosition(timeline, windowIndex,
C.TIME_UNSET, Math.max(0, defaultPositionProjectionUs));
if (defaultPosition == null) {
newLoadingPeriodIndex = C.INDEX_UNSET;
periodStartPositionUs = C.TIME_UNSET;
} else {
newLoadingPeriodIndex = defaultPosition.first;
periodStartPositionUs = defaultPosition.second;
}
}
if (newLoadingPeriodIndex != C.INDEX_UNSET) {
Object newPeriodUid = timeline.getPeriod(newLoadingPeriodIndex, period, true).uid;
MediaPeriod newMediaPeriod = mediaSource.createPeriod(newLoadingPeriodIndex,
loadControl.getAllocator(), periodStartPositionUs);
@ -1088,6 +1119,7 @@ import java.io.IOException;
setIsLoading(true);
}
}
}
if (loadingPeriodHolder == null || loadingPeriodHolder.isFullyBuffered()) {
setIsLoading(false);

View File

@ -117,7 +117,23 @@ public abstract class Timeline {
* null. The caller should pass false for efficiency reasons unless the field is required.
* @return The populated {@link Window}, for convenience.
*/
public abstract Window getWindow(int windowIndex, Window window, boolean setIds);
public Window getWindow(int windowIndex, Window window, boolean setIds) {
return getWindow(windowIndex, window, setIds, 0);
}
/**
* Populates a {@link Window} with data for the window at the specified index.
*
* @param windowIndex The index of the window.
* @param window The {@link Window} to populate. Must not be null.
* @param setIds Whether {@link Window#id} should be populated. If false, the field will be set to
* null. The caller should pass false for efficiency reasons unless the field is required.
* @param defaultPositionProjectionUs A duration into the future that the populated window's
* default start position should be projected.
* @return The populated {@link Window}, for convenience.
*/
public abstract Window getWindow(int windowIndex, Window window, boolean setIds,
long defaultPositionProjectionUs);
/**
* Returns the number of periods in the timeline.
@ -231,7 +247,9 @@ public abstract class Timeline {
/**
* Returns the default position relative to the start of the window at which to begin playback,
* in milliseconds.
* in milliseconds. May be {@link C#TIME_UNSET} if and only if the window was populated with a
* non-zero default position projection, and if the specified projection cannot be performed
* whilst remaining within the bounds of the window.
*/
public long getDefaultPositionMs() {
return C.usToMs(defaultPositionUs);
@ -239,7 +257,9 @@ public abstract class Timeline {
/**
* Returns the default position relative to the start of the window at which to begin playback,
* in microseconds.
* in microseconds. May be {@link C#TIME_UNSET} if and only if the window was populated with a
* non-zero default position projection, and if the specified projection cannot be performed
* whilst remaining within the bounds of the window.
*/
public long getDefaultPositionUs() {
return defaultPositionUs;

View File

@ -171,11 +171,13 @@ public final class ConcatenatingMediaSource implements MediaSource {
}
@Override
public Window getWindow(int windowIndex, Window window, boolean setIds) {
public Window getWindow(int windowIndex, Window window, boolean setIds,
long defaultPositionProjectionUs) {
int sourceIndex = getSourceIndexForWindow(windowIndex);
int firstWindowIndexInSource = getFirstWindowIndexInSource(sourceIndex);
int firstPeriodIndexInSource = getFirstPeriodIndexInSource(sourceIndex);
timelines[sourceIndex].getWindow(windowIndex - firstWindowIndexInSource, window, setIds);
timelines[sourceIndex].getWindow(windowIndex - firstWindowIndexInSource, window, setIds,
defaultPositionProjectionUs);
window.firstPeriodIndex += firstPeriodIndexInSource;
window.lastPeriodIndex += firstPeriodIndexInSource;
return window;

View File

@ -118,8 +118,10 @@ public final class LoopingMediaSource implements MediaSource {
}
@Override
public Window getWindow(int windowIndex, Window window, boolean setIds) {
childTimeline.getWindow(windowIndex % childWindowCount, window, setIds);
public Window getWindow(int windowIndex, Window window, boolean setIds,
long defaultPositionProjectionUs) {
childTimeline.getWindow(windowIndex % childWindowCount, window, setIds,
defaultPositionProjectionUs);
int periodIndexOffset = (windowIndex / childWindowCount) * childPeriodCount;
window.firstPeriodIndex += periodIndexOffset;
window.lastPeriodIndex += periodIndexOffset;

View File

@ -74,9 +74,18 @@ public final class SinglePeriodTimeline extends Timeline {
}
@Override
public Window getWindow(int windowIndex, Window window, boolean setIds) {
public Window getWindow(int windowIndex, Window window, boolean setIds,
long defaultPositionProjectionUs) {
Assertions.checkIndex(windowIndex, 0, 1);
Object id = setIds ? ID : null;
long windowDefaultStartPositionUs = this.windowDefaultStartPositionUs;
if (isDynamic) {
windowDefaultStartPositionUs += defaultPositionProjectionUs;
if (windowDefaultStartPositionUs > windowDurationUs) {
// The projection takes us beyond the end of the live window.
windowDefaultStartPositionUs = C.TIME_UNSET;
}
}
return window.set(id, C.TIME_UNSET, C.TIME_UNSET, isSeekable, isDynamic,
windowDefaultStartPositionUs, windowDurationUs, 0, 0, windowPositionInPeriodUs);
}

View File

@ -29,7 +29,6 @@ import com.google.android.exoplayer2.source.MediaPeriod;
import com.google.android.exoplayer2.source.MediaSource;
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.UtcTimingElement;
import com.google.android.exoplayer2.upstream.Allocator;
import com.google.android.exoplayer2.upstream.DataSource;
@ -400,39 +399,13 @@ public final class DashMediaSource implements MediaSource {
? manifest.suggestedPresentationDelay : DEFAULT_LIVE_PRESENTATION_DELAY_FIXED_MS;
}
// Snap the default position to the start of the segment containing it.
long defaultStartPositionUs = windowDurationUs - C.msToUs(presentationDelayForManifestMs);
if (defaultStartPositionUs < MIN_LIVE_DEFAULT_START_POSITION_US) {
windowDefaultStartPositionUs = windowDurationUs - C.msToUs(presentationDelayForManifestMs);
if (windowDefaultStartPositionUs < MIN_LIVE_DEFAULT_START_POSITION_US) {
// The default start position is too close to the start of the live window. Set it to the
// minimum default start position provided the window is at least twice as big. Else set
// it to the middle of the window.
defaultStartPositionUs = Math.min(MIN_LIVE_DEFAULT_START_POSITION_US, windowDurationUs / 2);
}
int periodIndex = 0;
long defaultStartPositionInPeriodUs = currentStartTimeUs + defaultStartPositionUs;
long periodDurationUs = manifest.getPeriodDurationUs(periodIndex);
while (periodIndex < manifest.getPeriodCount() - 1
&& defaultStartPositionInPeriodUs >= periodDurationUs) {
defaultStartPositionInPeriodUs -= periodDurationUs;
periodIndex++;
periodDurationUs = manifest.getPeriodDurationUs(periodIndex);
}
Period period = manifest.getPeriod(periodIndex);
int videoAdaptationSetIndex = period.getAdaptationSetIndex(C.TRACK_TYPE_VIDEO);
if (videoAdaptationSetIndex != C.INDEX_UNSET) {
// If there are multiple video adaptation sets with unaligned segments, the initial time may
// not correspond to the start of a segment in both, but this is an edge case.
DashSegmentIndex index =
period.adaptationSets.get(videoAdaptationSetIndex).representations.get(0).getIndex();
if (index != null) {
int segmentNum = index.getSegmentNum(defaultStartPositionInPeriodUs, periodDurationUs);
windowDefaultStartPositionUs =
defaultStartPositionUs - defaultStartPositionInPeriodUs + index.getTimeUs(segmentNum);
} else {
windowDefaultStartPositionUs = defaultStartPositionUs;
}
} else {
windowDefaultStartPositionUs = defaultStartPositionUs;
windowDefaultStartPositionUs = Math.min(MIN_LIVE_DEFAULT_START_POSITION_US,
windowDurationUs / 2);
}
}
long windowStartTimeMs = manifest.availabilityStartTime
@ -561,8 +534,11 @@ public final class DashMediaSource implements MediaSource {
}
@Override
public Window getWindow(int windowIndex, Window window, boolean setIdentifier) {
public Window getWindow(int windowIndex, Window window, boolean setIdentifier,
long defaultPositionProjectionUs) {
Assertions.checkIndex(windowIndex, 0, 1);
long windowDefaultStartPositionUs = getAdjustedWindowDefaultStartPositionUs(
defaultPositionProjectionUs);
return window.set(null, presentationStartTimeMs, windowStartTimeMs, true /* isSeekable */,
manifest.dynamic, windowDefaultStartPositionUs, windowDurationUs, 0,
manifest.getPeriodCount() - 1, offsetInFirstPeriodUs);
@ -578,6 +554,48 @@ public final class DashMediaSource implements MediaSource {
? C.INDEX_UNSET : (periodId - firstPeriodId);
}
private long getAdjustedWindowDefaultStartPositionUs(long defaultPositionProjectionUs) {
long windowDefaultStartPositionUs = this.windowDefaultStartPositionUs;
if (!manifest.dynamic) {
return windowDefaultStartPositionUs;
}
if (defaultPositionProjectionUs > 0) {
windowDefaultStartPositionUs += defaultPositionProjectionUs;
if (windowDefaultStartPositionUs > windowDurationUs) {
// The projection takes us beyond the end of the live window.
return C.TIME_UNSET;
}
}
// Attempt to snap to the start of the corresponding video segment.
int periodIndex = 0;
long defaultStartPositionInPeriodUs = offsetInFirstPeriodUs + windowDefaultStartPositionUs;
long periodDurationUs = manifest.getPeriodDurationUs(periodIndex);
while (periodIndex < manifest.getPeriodCount() - 1
&& defaultStartPositionInPeriodUs >= periodDurationUs) {
defaultStartPositionInPeriodUs -= periodDurationUs;
periodIndex++;
periodDurationUs = manifest.getPeriodDurationUs(periodIndex);
}
com.google.android.exoplayer2.source.dash.manifest.Period period =
manifest.getPeriod(periodIndex);
int videoAdaptationSetIndex = period.getAdaptationSetIndex(C.TRACK_TYPE_VIDEO);
if (videoAdaptationSetIndex == C.INDEX_UNSET) {
// No video adaptation set for snapping.
return windowDefaultStartPositionUs;
}
// If there are multiple video adaptation sets with unaligned segments, the initial time may
// not correspond to the start of a segment in both, but this is an edge case.
DashSegmentIndex snapIndex = period.adaptationSets.get(videoAdaptationSetIndex)
.representations.get(0).getIndex();
if (snapIndex == null) {
// Video adaptation set does not include an index for snapping.
return windowDefaultStartPositionUs;
}
int segmentNum = snapIndex.getSegmentNum(defaultStartPositionInPeriodUs, periodDurationUs);
return windowDefaultStartPositionUs + snapIndex.getTimeUs(segmentNum)
- defaultStartPositionInPeriodUs;
}
}
private final class ManifestCallback implements