Enable SeekParameters functionality for ExtractorMediaSource

Also fix ClippingMediaSource to consider the start position an
artificial key-frame, and to properly offset the value returned
by getAdjustedSeekPositionUs.

Issue: #2882

-------------
Created by MOE: https://github.com/google/moe
MOE_MIGRATED_REVID=179032243
This commit is contained in:
olly 2017-12-14 05:18:07 -08:00 committed by Oliver Woodman
parent a17375b7d3
commit 37a275f67e
6 changed files with 126 additions and 17 deletions

View File

@ -16,6 +16,12 @@
sub-streams, by allowing injection of custom `CompositeSequenceableLoader`
factories through `DashMediaSource.Factory`, `HlsMediaSource.Factory`,
`SsMediaSource.Factory`, and `MergingMediaSource`.
* Add `ExoPlayer.setSeekParameters` for controlling how seek operations are
performed. The `SeekParameters` class contains defaults for exact seeking and
seeking to the closest sync points before, either side or after specified seek
positions.
* Note: `SeekParameters` are only currently effective when playing
`ExtractorMediaSource`s (i.e. progressive streams).
* DASH: Support DASH manifest EventStream elements.
* HLS: Add opt-in support for chunkless preparation in HLS. This allows an
HLS source to finish preparation without downloading any chunks, which can

View File

@ -678,20 +678,20 @@ import java.io.IOException;
periodPositionUs = 0;
}
try {
long newPeriodPositionUs = periodPositionUs;
if (periodId.equals(playbackInfo.periodId)) {
long adjustedPeriodPositionUs = periodPositionUs;
if (playingPeriodHolder != null) {
adjustedPeriodPositionUs =
if (playingPeriodHolder != null && newPeriodPositionUs != 0) {
newPeriodPositionUs =
playingPeriodHolder.mediaPeriod.getAdjustedSeekPositionUs(
adjustedPeriodPositionUs, SeekParameters.DEFAULT);
newPeriodPositionUs, seekParameters);
}
if ((adjustedPeriodPositionUs / 1000) == (playbackInfo.positionUs / 1000)) {
if ((newPeriodPositionUs / 1000) == (playbackInfo.positionUs / 1000)) {
// Seek will be performed to the current position. Do nothing.
periodPositionUs = playbackInfo.positionUs;
return;
}
}
long newPeriodPositionUs = seekToPeriodPosition(periodId, periodPositionUs);
newPeriodPositionUs = seekToPeriodPosition(periodId, newPeriodPositionUs);
seekPositionAdjusted |= periodPositionUs != newPeriodPositionUs;
periodPositionUs = newPeriodPositionUs;
} finally {

View File

@ -165,16 +165,23 @@ public final class ClippingMediaPeriod implements MediaPeriod, MediaPeriod.Callb
sampleStream.clearSentEos();
}
}
long seekUs = mediaPeriod.seekToUs(positionUs + startUs);
Assertions.checkState(seekUs == positionUs + startUs
long offsetPositionUs = positionUs + startUs;
long seekUs = mediaPeriod.seekToUs(offsetPositionUs);
Assertions.checkState(
seekUs == offsetPositionUs
|| (seekUs >= startUs && (endUs == C.TIME_END_OF_SOURCE || seekUs <= endUs)));
return seekUs - startUs;
}
@Override
public long getAdjustedSeekPositionUs(long positionUs, SeekParameters seekParameters) {
return mediaPeriod.getAdjustedSeekPositionUs(
positionUs + startUs, adjustSeekParameters(positionUs + startUs, seekParameters));
if (positionUs == startUs) {
// Never adjust seeks to the start of the clipped view.
return 0;
}
long offsetPositionUs = positionUs + startUs;
SeekParameters clippedSeekParameters = clipSeekParameters(offsetPositionUs, seekParameters);
return mediaPeriod.getAdjustedSeekPositionUs(offsetPositionUs, clippedSeekParameters) - startUs;
}
@Override
@ -209,12 +216,12 @@ public final class ClippingMediaPeriod implements MediaPeriod, MediaPeriod.Callb
return pendingInitialDiscontinuityPositionUs != C.TIME_UNSET;
}
private SeekParameters adjustSeekParameters(long positionUs, SeekParameters seekParameters) {
long toleranceBeforeMs = Math.min(positionUs - startUs, seekParameters.toleranceBeforeUs);
private SeekParameters clipSeekParameters(long offsetPositionUs, SeekParameters seekParameters) {
long toleranceBeforeMs = Math.min(offsetPositionUs - startUs, seekParameters.toleranceBeforeUs);
long toleranceAfterMs =
endUs == C.TIME_END_OF_SOURCE
? seekParameters.toleranceAfterUs
: Math.min(endUs - positionUs, seekParameters.toleranceAfterUs);
: Math.min(endUs - offsetPositionUs, seekParameters.toleranceAfterUs);
if (toleranceBeforeMs == seekParameters.toleranceBeforeUs
&& toleranceAfterMs == seekParameters.toleranceAfterUs) {
return seekParameters;

View File

@ -29,6 +29,7 @@ import com.google.android.exoplayer2.extractor.ExtractorInput;
import com.google.android.exoplayer2.extractor.ExtractorOutput;
import com.google.android.exoplayer2.extractor.PositionHolder;
import com.google.android.exoplayer2.extractor.SeekMap;
import com.google.android.exoplayer2.extractor.SeekMap.SeekPoints;
import com.google.android.exoplayer2.extractor.TrackOutput;
import com.google.android.exoplayer2.source.MediaSourceEventListener.EventDispatcher;
import com.google.android.exoplayer2.source.SampleQueue.UpstreamFormatChangedListener;
@ -372,8 +373,33 @@ import java.util.Arrays;
@Override
public long getAdjustedSeekPositionUs(long positionUs, SeekParameters seekParameters) {
if (!seekMap.isSeekable()) {
// Treat all seeks into non-seekable media as being to t=0.
return seekMap.isSeekable() ? positionUs : 0;
return 0;
}
SeekPoints seekPoints = seekMap.getSeekPoints(positionUs);
long minPositionUs =
Util.subtractWithOverflowDefault(
positionUs, seekParameters.toleranceBeforeUs, Long.MIN_VALUE);
long maxPositionUs =
Util.addWithOverflowDefault(positionUs, seekParameters.toleranceAfterUs, Long.MAX_VALUE);
long firstPointUs = seekPoints.first.timeUs;
boolean firstPointValid = minPositionUs <= firstPointUs && firstPointUs <= maxPositionUs;
long secondPointUs = seekPoints.second.timeUs;
boolean secondPointValid = minPositionUs <= secondPointUs && secondPointUs <= maxPositionUs;
if (firstPointValid && secondPointValid) {
if (Math.abs(firstPointUs - positionUs) <= Math.abs(secondPointUs - positionUs)) {
return firstPointUs;
} else {
return secondPointUs;
}
} else if (firstPointValid) {
return firstPointUs;
} else if (secondPointValid) {
return secondPointUs;
} else {
return minPositionUs;
}
}
// SampleStream methods.
@ -657,7 +683,7 @@ import java.util.Arrays;
return pendingResetPositionUs != C.TIME_UNSET;
}
private boolean isLoadableExceptionFatal(IOException e) {
private static boolean isLoadableExceptionFatal(IOException e) {
return e instanceof UnrecognizedInputFormatException;
}

View File

@ -362,6 +362,40 @@ public final class Util {
return Math.max(min, Math.min(value, max));
}
/**
* Returns the sum of two arguments, or a third argument if the result overflows.
*
* @param x The first value.
* @param y The second value.
* @param overflowResult The return value if {@code x + y} overflows.
* @return {@code x + y}, or {@code overflowResult} if the result overflows.
*/
public static long addWithOverflowDefault(long x, long y, long overflowResult) {
long result = x + y;
// See Hacker's Delight 2-13 (H. Warren Jr).
if (((x ^ result) & (y ^ result)) < 0) {
return overflowResult;
}
return result;
}
/**
* Returns the difference between two arguments, or a third argument if the result overflows.
*
* @param x The first value.
* @param y The second value.
* @param overflowResult The return value if {@code x - y} overflows.
* @return {@code x - y}, or {@code overflowResult} if the result overflows.
*/
public static long subtractWithOverflowDefault(long x, long y, long overflowResult) {
long result = x - y;
// See Hacker's Delight 2-13 (H. Warren Jr).
if (((x ^ y) & (x ^ result)) < 0) {
return overflowResult;
}
return result;
}
/**
* Returns the index of the largest element in {@code array} that is less than (or optionally
* equal to) a specified {@code value}.

View File

@ -41,6 +41,42 @@ import org.robolectric.annotation.Config;
@Config(sdk = Config.TARGET_SDK, manifest = Config.NONE)
public class UtilTest {
@Test
public void testAddWithOverflowDefault() {
long res = Util.addWithOverflowDefault(5, 10, /* overflowResult= */ 0);
assertThat(res).isEqualTo(15);
res = Util.addWithOverflowDefault(Long.MAX_VALUE - 1, 1, /* overflowResult= */ 12345);
assertThat(res).isEqualTo(Long.MAX_VALUE);
res = Util.addWithOverflowDefault(Long.MIN_VALUE + 1, -1, /* overflowResult= */ 12345);
assertThat(res).isEqualTo(Long.MIN_VALUE);
res = Util.addWithOverflowDefault(Long.MAX_VALUE, 1, /* overflowResult= */ 12345);
assertThat(res).isEqualTo(12345);
res = Util.addWithOverflowDefault(Long.MIN_VALUE, -1, /* overflowResult= */ 12345);
assertThat(res).isEqualTo(12345);
}
@Test
public void testSubtrackWithOverflowDefault() {
long res = Util.subtractWithOverflowDefault(5, 10, /* overflowResult= */ 0);
assertThat(res).isEqualTo(-5);
res = Util.subtractWithOverflowDefault(Long.MIN_VALUE + 1, 1, /* overflowResult= */ 12345);
assertThat(res).isEqualTo(Long.MIN_VALUE);
res = Util.subtractWithOverflowDefault(Long.MAX_VALUE - 1, -1, /* overflowResult= */ 12345);
assertThat(res).isEqualTo(Long.MAX_VALUE);
res = Util.subtractWithOverflowDefault(Long.MIN_VALUE, 1, /* overflowResult= */ 12345);
assertThat(res).isEqualTo(12345);
res = Util.subtractWithOverflowDefault(Long.MAX_VALUE, -1, /* overflowResult= */ 12345);
assertThat(res).isEqualTo(12345);
}
@Test
public void testInferContentType() {
assertThat(Util.inferContentType("http://a.b/c.ism")).isEqualTo(C.TYPE_SS);