mirror of
https://github.com/androidx/media.git
synced 2025-05-17 04:29:55 +08:00
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:
parent
89ad5e6db3
commit
7b0effc2d0
@ -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);
|
||||
|
@ -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;
|
||||
|
@ -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;
|
||||
|
@ -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;
|
||||
|
@ -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);
|
||||
}
|
||||
|
@ -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
|
||||
|
Loading…
x
Reference in New Issue
Block a user