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` sub-streams, by allowing injection of custom `CompositeSequenceableLoader`
factories through `DashMediaSource.Factory`, `HlsMediaSource.Factory`, factories through `DashMediaSource.Factory`, `HlsMediaSource.Factory`,
`SsMediaSource.Factory`, and `MergingMediaSource`. `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. * DASH: Support DASH manifest EventStream elements.
* HLS: Add opt-in support for chunkless preparation in HLS. This allows an * HLS: Add opt-in support for chunkless preparation in HLS. This allows an
HLS source to finish preparation without downloading any chunks, which can HLS source to finish preparation without downloading any chunks, which can

View File

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

View File

@ -165,16 +165,23 @@ public final class ClippingMediaPeriod implements MediaPeriod, MediaPeriod.Callb
sampleStream.clearSentEos(); sampleStream.clearSentEos();
} }
} }
long seekUs = mediaPeriod.seekToUs(positionUs + startUs); long offsetPositionUs = positionUs + startUs;
Assertions.checkState(seekUs == positionUs + startUs long seekUs = mediaPeriod.seekToUs(offsetPositionUs);
|| (seekUs >= startUs && (endUs == C.TIME_END_OF_SOURCE || seekUs <= endUs))); Assertions.checkState(
seekUs == offsetPositionUs
|| (seekUs >= startUs && (endUs == C.TIME_END_OF_SOURCE || seekUs <= endUs)));
return seekUs - startUs; return seekUs - startUs;
} }
@Override @Override
public long getAdjustedSeekPositionUs(long positionUs, SeekParameters seekParameters) { public long getAdjustedSeekPositionUs(long positionUs, SeekParameters seekParameters) {
return mediaPeriod.getAdjustedSeekPositionUs( if (positionUs == startUs) {
positionUs + startUs, adjustSeekParameters(positionUs + startUs, seekParameters)); // 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 @Override
@ -209,12 +216,12 @@ public final class ClippingMediaPeriod implements MediaPeriod, MediaPeriod.Callb
return pendingInitialDiscontinuityPositionUs != C.TIME_UNSET; return pendingInitialDiscontinuityPositionUs != C.TIME_UNSET;
} }
private SeekParameters adjustSeekParameters(long positionUs, SeekParameters seekParameters) { private SeekParameters clipSeekParameters(long offsetPositionUs, SeekParameters seekParameters) {
long toleranceBeforeMs = Math.min(positionUs - startUs, seekParameters.toleranceBeforeUs); long toleranceBeforeMs = Math.min(offsetPositionUs - startUs, seekParameters.toleranceBeforeUs);
long toleranceAfterMs = long toleranceAfterMs =
endUs == C.TIME_END_OF_SOURCE endUs == C.TIME_END_OF_SOURCE
? seekParameters.toleranceAfterUs ? seekParameters.toleranceAfterUs
: Math.min(endUs - positionUs, seekParameters.toleranceAfterUs); : Math.min(endUs - offsetPositionUs, seekParameters.toleranceAfterUs);
if (toleranceBeforeMs == seekParameters.toleranceBeforeUs if (toleranceBeforeMs == seekParameters.toleranceBeforeUs
&& toleranceAfterMs == seekParameters.toleranceAfterUs) { && toleranceAfterMs == seekParameters.toleranceAfterUs) {
return seekParameters; 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.ExtractorOutput;
import com.google.android.exoplayer2.extractor.PositionHolder; import com.google.android.exoplayer2.extractor.PositionHolder;
import com.google.android.exoplayer2.extractor.SeekMap; 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.extractor.TrackOutput;
import com.google.android.exoplayer2.source.MediaSourceEventListener.EventDispatcher; import com.google.android.exoplayer2.source.MediaSourceEventListener.EventDispatcher;
import com.google.android.exoplayer2.source.SampleQueue.UpstreamFormatChangedListener; import com.google.android.exoplayer2.source.SampleQueue.UpstreamFormatChangedListener;
@ -372,8 +373,33 @@ import java.util.Arrays;
@Override @Override
public long getAdjustedSeekPositionUs(long positionUs, SeekParameters seekParameters) { public long getAdjustedSeekPositionUs(long positionUs, SeekParameters seekParameters) {
// Treat all seeks into non-seekable media as being to t=0. if (!seekMap.isSeekable()) {
return seekMap.isSeekable() ? positionUs : 0; // Treat all seeks into non-seekable media as being to t=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. // SampleStream methods.
@ -657,7 +683,7 @@ import java.util.Arrays;
return pendingResetPositionUs != C.TIME_UNSET; return pendingResetPositionUs != C.TIME_UNSET;
} }
private boolean isLoadableExceptionFatal(IOException e) { private static boolean isLoadableExceptionFatal(IOException e) {
return e instanceof UnrecognizedInputFormatException; return e instanceof UnrecognizedInputFormatException;
} }

View File

@ -362,6 +362,40 @@ public final class Util {
return Math.max(min, Math.min(value, max)); 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 * Returns the index of the largest element in {@code array} that is less than (or optionally
* equal to) a specified {@code value}. * 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) @Config(sdk = Config.TARGET_SDK, manifest = Config.NONE)
public class UtilTest { 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 @Test
public void testInferContentType() { public void testInferContentType() {
assertThat(Util.inferContentType("http://a.b/c.ism")).isEqualTo(C.TYPE_SS); assertThat(Util.inferContentType("http://a.b/c.ism")).isEqualTo(C.TYPE_SS);