mirror of
https://github.com/androidx/media.git
synced 2025-05-03 21:57:46 +08:00
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:
parent
a17375b7d3
commit
37a275f67e
@ -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
|
||||||
|
@ -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 {
|
||||||
|
@ -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;
|
||||||
|
@ -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;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -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}.
|
||||||
|
@ -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);
|
||||||
|
Loading…
x
Reference in New Issue
Block a user