Add flag for constant bitrate seeking even if input length is unknown
PiperOrigin-RevId: 396363113
This commit is contained in:
parent
cf0ec91934
commit
f8d60e2bbb
@ -16,9 +16,9 @@
|
||||
package com.google.android.exoplayer2.extractor;
|
||||
|
||||
import static java.lang.Math.max;
|
||||
import static java.lang.Math.min;
|
||||
|
||||
import com.google.android.exoplayer2.C;
|
||||
import com.google.android.exoplayer2.util.Util;
|
||||
|
||||
/**
|
||||
* A {@link SeekMap} implementation that assumes the stream has a constant bitrate and consists of
|
||||
@ -33,9 +33,10 @@ public class ConstantBitrateSeekMap implements SeekMap {
|
||||
private final long dataSize;
|
||||
private final int bitrate;
|
||||
private final long durationUs;
|
||||
private final boolean allowSeeksIfLengthUnknown;
|
||||
|
||||
/**
|
||||
* Constructs a new instance from a stream.
|
||||
* Creates an instance with {@code allowSeeksIfLengthUnknown} set to {@code false}.
|
||||
*
|
||||
* @param inputLength The length of the stream in bytes, or {@link C#LENGTH_UNSET} if unknown.
|
||||
* @param firstFrameBytePosition The byte-position of the first frame in the stream.
|
||||
@ -45,10 +46,36 @@ public class ConstantBitrateSeekMap implements SeekMap {
|
||||
*/
|
||||
public ConstantBitrateSeekMap(
|
||||
long inputLength, long firstFrameBytePosition, int bitrate, int frameSize) {
|
||||
this(
|
||||
inputLength,
|
||||
firstFrameBytePosition,
|
||||
bitrate,
|
||||
frameSize,
|
||||
/* allowSeeksIfLengthUnknown= */ false);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates an instance.
|
||||
*
|
||||
* @param inputLength The length of the stream in bytes, or {@link C#LENGTH_UNSET} if unknown.
|
||||
* @param firstFrameBytePosition The byte-position of the first frame in the stream.
|
||||
* @param bitrate The bitrate (which is assumed to be constant in the stream).
|
||||
* @param frameSize The size of each frame in the stream in bytes. May be {@link C#LENGTH_UNSET}
|
||||
* if unknown.
|
||||
* @param allowSeeksIfLengthUnknown Whether to allow seeking even if the length of the content is
|
||||
* unknown.
|
||||
*/
|
||||
public ConstantBitrateSeekMap(
|
||||
long inputLength,
|
||||
long firstFrameBytePosition,
|
||||
int bitrate,
|
||||
int frameSize,
|
||||
boolean allowSeeksIfLengthUnknown) {
|
||||
this.inputLength = inputLength;
|
||||
this.firstFrameBytePosition = firstFrameBytePosition;
|
||||
this.frameSize = frameSize == C.LENGTH_UNSET ? 1 : frameSize;
|
||||
this.bitrate = bitrate;
|
||||
this.allowSeeksIfLengthUnknown = allowSeeksIfLengthUnknown;
|
||||
|
||||
if (inputLength == C.LENGTH_UNSET) {
|
||||
dataSize = C.LENGTH_UNSET;
|
||||
@ -61,18 +88,23 @@ public class ConstantBitrateSeekMap implements SeekMap {
|
||||
|
||||
@Override
|
||||
public boolean isSeekable() {
|
||||
return dataSize != C.LENGTH_UNSET;
|
||||
return dataSize != C.LENGTH_UNSET || allowSeeksIfLengthUnknown;
|
||||
}
|
||||
|
||||
@Override
|
||||
public SeekPoints getSeekPoints(long timeUs) {
|
||||
if (dataSize == C.LENGTH_UNSET) {
|
||||
if (dataSize == C.LENGTH_UNSET && !allowSeeksIfLengthUnknown) {
|
||||
return new SeekPoints(new SeekPoint(0, firstFrameBytePosition));
|
||||
}
|
||||
long seekFramePosition = getFramePositionForTimeUs(timeUs);
|
||||
long seekTimeUs = getTimeUsAtPosition(seekFramePosition);
|
||||
SeekPoint seekPoint = new SeekPoint(seekTimeUs, seekFramePosition);
|
||||
if (seekTimeUs >= timeUs || seekFramePosition + frameSize >= inputLength) {
|
||||
// We only return a single seek point if the length is unknown, to avoid generating a second
|
||||
// seek point beyond the end of the data in the case that the requested seek position is valid,
|
||||
// but very close to the end of the content.
|
||||
if (dataSize == C.LENGTH_UNSET
|
||||
|| seekTimeUs >= timeUs
|
||||
|| seekFramePosition + frameSize >= inputLength) {
|
||||
return new SeekPoints(seekPoint);
|
||||
} else {
|
||||
long secondSeekPosition = seekFramePosition + frameSize;
|
||||
@ -118,8 +150,10 @@ public class ConstantBitrateSeekMap implements SeekMap {
|
||||
long positionOffset = (timeUs * bitrate) / (C.MICROS_PER_SECOND * C.BITS_PER_BYTE);
|
||||
// Constrain to nearest preceding frame offset.
|
||||
positionOffset = (positionOffset / frameSize) * frameSize;
|
||||
positionOffset =
|
||||
Util.constrainValue(positionOffset, /* min= */ 0, /* max= */ dataSize - frameSize);
|
||||
if (dataSize != C.LENGTH_UNSET) {
|
||||
positionOffset = min(positionOffset, dataSize - frameSize);
|
||||
}
|
||||
positionOffset = max(positionOffset, 0);
|
||||
return firstFrameBytePosition + positionOffset;
|
||||
}
|
||||
}
|
||||
|
@ -20,6 +20,8 @@ import static com.google.android.exoplayer2.util.FileTypes.inferFileTypeFromUri;
|
||||
|
||||
import android.net.Uri;
|
||||
import androidx.annotation.Nullable;
|
||||
import com.google.android.exoplayer2.PlaybackException;
|
||||
import com.google.android.exoplayer2.Player;
|
||||
import com.google.android.exoplayer2.extractor.amr.AmrExtractor;
|
||||
import com.google.android.exoplayer2.extractor.flac.FlacExtractor;
|
||||
import com.google.android.exoplayer2.extractor.flv.FlvExtractor;
|
||||
@ -125,6 +127,7 @@ public final class DefaultExtractorsFactory implements ExtractorsFactory {
|
||||
}
|
||||
|
||||
private boolean constantBitrateSeekingEnabled;
|
||||
private boolean constantBitrateSeekingAlwaysEnabled;
|
||||
@AdtsExtractor.Flags private int adtsFlags;
|
||||
@AmrExtractor.Flags private int amrFlags;
|
||||
@FlacExtractor.Flags private int flacFlags;
|
||||
@ -158,6 +161,30 @@ public final class DefaultExtractorsFactory implements ExtractorsFactory {
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Convenience method to set whether approximate seeking using constant bitrate assumptions should
|
||||
* be enabled for all extractors that support it, and if it should be enabled even if the content
|
||||
* length (and hence the duration of the media) is unknown. If set to true, the flags required to
|
||||
* enable this functionality will be OR'd with those passed to the setters when creating extractor
|
||||
* instances. If set to false then the flags passed to the setters will be used without
|
||||
* modification.
|
||||
*
|
||||
* <p>When seeking into content where the length is unknown, application code should ensure that
|
||||
* requested seek positions are valid, or should be ready to handle playback failures reported
|
||||
* through {@link Player.Listener#onPlayerError} with {@link PlaybackException#errorCode} set to
|
||||
* {@link PlaybackException#ERROR_CODE_IO_READ_POSITION_OUT_OF_RANGE}.
|
||||
*
|
||||
* @param constantBitrateSeekingAlwaysEnabled Whether approximate seeking using a constant bitrate
|
||||
* assumption should be enabled for all extractors that support it, including when the content
|
||||
* duration is unknown.
|
||||
* @return The factory, for convenience.
|
||||
*/
|
||||
public synchronized DefaultExtractorsFactory setConstantBitrateSeekingAlwaysEnabled(
|
||||
boolean constantBitrateSeekingAlwaysEnabled) {
|
||||
this.constantBitrateSeekingAlwaysEnabled = constantBitrateSeekingAlwaysEnabled;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets flags for {@link AdtsExtractor} instances created by the factory.
|
||||
*
|
||||
@ -333,6 +360,9 @@ public final class DefaultExtractorsFactory implements ExtractorsFactory {
|
||||
adtsFlags
|
||||
| (constantBitrateSeekingEnabled
|
||||
? AdtsExtractor.FLAG_ENABLE_CONSTANT_BITRATE_SEEKING
|
||||
: 0)
|
||||
| (constantBitrateSeekingAlwaysEnabled
|
||||
? AdtsExtractor.FLAG_ENABLE_CONSTANT_BITRATE_SEEKING_ALWAYS
|
||||
: 0)));
|
||||
break;
|
||||
case FileTypes.AMR:
|
||||
@ -341,6 +371,9 @@ public final class DefaultExtractorsFactory implements ExtractorsFactory {
|
||||
amrFlags
|
||||
| (constantBitrateSeekingEnabled
|
||||
? AmrExtractor.FLAG_ENABLE_CONSTANT_BITRATE_SEEKING
|
||||
: 0)
|
||||
| (constantBitrateSeekingAlwaysEnabled
|
||||
? AmrExtractor.FLAG_ENABLE_CONSTANT_BITRATE_SEEKING_ALWAYS
|
||||
: 0)));
|
||||
break;
|
||||
case FileTypes.FLAC:
|
||||
@ -367,6 +400,9 @@ public final class DefaultExtractorsFactory implements ExtractorsFactory {
|
||||
mp3Flags
|
||||
| (constantBitrateSeekingEnabled
|
||||
? Mp3Extractor.FLAG_ENABLE_CONSTANT_BITRATE_SEEKING
|
||||
: 0)
|
||||
| (constantBitrateSeekingAlwaysEnabled
|
||||
? Mp3Extractor.FLAG_ENABLE_CONSTANT_BITRATE_SEEKING_ALWAYS
|
||||
: 0)));
|
||||
break;
|
||||
case FileTypes.MP4:
|
||||
|
@ -19,6 +19,8 @@ import androidx.annotation.IntDef;
|
||||
import com.google.android.exoplayer2.C;
|
||||
import com.google.android.exoplayer2.Format;
|
||||
import com.google.android.exoplayer2.ParserException;
|
||||
import com.google.android.exoplayer2.PlaybackException;
|
||||
import com.google.android.exoplayer2.Player;
|
||||
import com.google.android.exoplayer2.extractor.ConstantBitrateSeekMap;
|
||||
import com.google.android.exoplayer2.extractor.Extractor;
|
||||
import com.google.android.exoplayer2.extractor.ExtractorInput;
|
||||
@ -52,20 +54,33 @@ public final class AmrExtractor implements Extractor {
|
||||
public static final ExtractorsFactory FACTORY = () -> new Extractor[] {new AmrExtractor()};
|
||||
|
||||
/**
|
||||
* Flags controlling the behavior of the extractor. Possible flag value is {@link
|
||||
* #FLAG_ENABLE_CONSTANT_BITRATE_SEEKING}.
|
||||
* Flags controlling the behavior of the extractor. Possible flag values are {@link
|
||||
* #FLAG_ENABLE_CONSTANT_BITRATE_SEEKING} and {@link
|
||||
* #FLAG_ENABLE_CONSTANT_BITRATE_SEEKING_ALWAYS}.
|
||||
*/
|
||||
@Documented
|
||||
@Retention(RetentionPolicy.SOURCE)
|
||||
@IntDef(
|
||||
flag = true,
|
||||
value = {FLAG_ENABLE_CONSTANT_BITRATE_SEEKING})
|
||||
value = {FLAG_ENABLE_CONSTANT_BITRATE_SEEKING, FLAG_ENABLE_CONSTANT_BITRATE_SEEKING_ALWAYS})
|
||||
public @interface Flags {}
|
||||
/**
|
||||
* Flag to force enable seeking using a constant bitrate assumption in cases where seeking would
|
||||
* otherwise not be possible.
|
||||
*/
|
||||
public static final int FLAG_ENABLE_CONSTANT_BITRATE_SEEKING = 1;
|
||||
/**
|
||||
* Like {@link #FLAG_ENABLE_CONSTANT_BITRATE_SEEKING}, except that seeking is also enabled in
|
||||
* cases where the content length (and hence the duration of the media) is unknown. Application
|
||||
* code should ensure that requested seek positions are valid when using this flag, or be ready to
|
||||
* handle playback failures reported through {@link Player.Listener#onPlayerError} with {@link
|
||||
* PlaybackException#errorCode} set to {@link
|
||||
* PlaybackException#ERROR_CODE_IO_READ_POSITION_OUT_OF_RANGE}.
|
||||
*
|
||||
* <p>If this flag is set, then the behavior enabled by {@link
|
||||
* #FLAG_ENABLE_CONSTANT_BITRATE_SEEKING} is implicitly enabled as well.
|
||||
*/
|
||||
public static final int FLAG_ENABLE_CONSTANT_BITRATE_SEEKING_ALWAYS = 1 << 1;
|
||||
|
||||
/**
|
||||
* The frame size in bytes, including header (1 byte), for each of the 16 frame types for AMR
|
||||
@ -152,6 +167,9 @@ public final class AmrExtractor implements Extractor {
|
||||
|
||||
/** @param flags Flags that control the extractor's behavior. */
|
||||
public AmrExtractor(@Flags int flags) {
|
||||
if ((flags & FLAG_ENABLE_CONSTANT_BITRATE_SEEKING_ALWAYS) != 0) {
|
||||
flags |= FLAG_ENABLE_CONSTANT_BITRATE_SEEKING;
|
||||
}
|
||||
this.flags = flags;
|
||||
scratch = new byte[1];
|
||||
firstSampleSize = C.LENGTH_UNSET;
|
||||
@ -360,15 +378,18 @@ public final class AmrExtractor implements Extractor {
|
||||
hasOutputSeekMap = true;
|
||||
} else if (numSamplesWithSameSize >= NUM_SAME_SIZE_CONSTANT_BIT_RATE_THRESHOLD
|
||||
|| sampleReadResult == RESULT_END_OF_INPUT) {
|
||||
seekMap = getConstantBitrateSeekMap(inputLength);
|
||||
seekMap =
|
||||
getConstantBitrateSeekMap(
|
||||
inputLength, (flags & FLAG_ENABLE_CONSTANT_BITRATE_SEEKING_ALWAYS) != 0);
|
||||
extractorOutput.seekMap(seekMap);
|
||||
hasOutputSeekMap = true;
|
||||
}
|
||||
}
|
||||
|
||||
private SeekMap getConstantBitrateSeekMap(long inputLength) {
|
||||
private SeekMap getConstantBitrateSeekMap(long inputLength, boolean allowSeeksIfLengthUnknown) {
|
||||
int bitrate = getBitrateFromFrameSize(firstSampleSize, SAMPLE_TIME_PER_FRAME_US);
|
||||
return new ConstantBitrateSeekMap(inputLength, firstSamplePosition, bitrate, firstSampleSize);
|
||||
return new ConstantBitrateSeekMap(
|
||||
inputLength, firstSamplePosition, bitrate, firstSampleSize, allowSeeksIfLengthUnknown);
|
||||
}
|
||||
|
||||
@EnsuresNonNull({"extractorOutput", "trackOutput"})
|
||||
|
@ -28,13 +28,23 @@ import com.google.android.exoplayer2.extractor.ConstantBitrateSeekMap;
|
||||
* @param inputLength The length of the stream in bytes, or {@link C#LENGTH_UNSET} if unknown.
|
||||
* @param firstFramePosition The position of the first frame in the stream.
|
||||
* @param mpegAudioHeader The MPEG audio header associated with the first frame.
|
||||
* @param allowSeeksIfLengthUnknown Whether to allow seeking even if the length of the content is
|
||||
* unknown.
|
||||
*/
|
||||
public ConstantBitrateSeeker(
|
||||
long inputLength, long firstFramePosition, MpegAudioUtil.Header mpegAudioHeader) {
|
||||
long inputLength,
|
||||
long firstFramePosition,
|
||||
MpegAudioUtil.Header mpegAudioHeader,
|
||||
boolean allowSeeksIfLengthUnknown) {
|
||||
// Set the seeker frame size to the size of the first frame (even though some constant bitrate
|
||||
// streams have variable frame sizes) to avoid the need to re-synchronize for constant frame
|
||||
// size streams.
|
||||
super(inputLength, firstFramePosition, mpegAudioHeader.bitrate, mpegAudioHeader.frameSize);
|
||||
super(
|
||||
inputLength,
|
||||
firstFramePosition,
|
||||
mpegAudioHeader.bitrate,
|
||||
mpegAudioHeader.frameSize,
|
||||
allowSeeksIfLengthUnknown);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -20,6 +20,8 @@ import androidx.annotation.Nullable;
|
||||
import com.google.android.exoplayer2.C;
|
||||
import com.google.android.exoplayer2.Format;
|
||||
import com.google.android.exoplayer2.ParserException;
|
||||
import com.google.android.exoplayer2.PlaybackException;
|
||||
import com.google.android.exoplayer2.Player;
|
||||
import com.google.android.exoplayer2.audio.MpegAudioUtil;
|
||||
import com.google.android.exoplayer2.extractor.DummyTrackOutput;
|
||||
import com.google.android.exoplayer2.extractor.Extractor;
|
||||
@ -56,8 +58,8 @@ public final class Mp3Extractor implements Extractor {
|
||||
|
||||
/**
|
||||
* Flags controlling the behavior of the extractor. Possible flag values are {@link
|
||||
* #FLAG_ENABLE_CONSTANT_BITRATE_SEEKING}, {@link #FLAG_ENABLE_INDEX_SEEKING} and {@link
|
||||
* #FLAG_DISABLE_ID3_METADATA}.
|
||||
* #FLAG_ENABLE_CONSTANT_BITRATE_SEEKING}, {@link #FLAG_ENABLE_CONSTANT_BITRATE_SEEKING_ALWAYS},
|
||||
* {@link #FLAG_ENABLE_INDEX_SEEKING} and {@link #FLAG_DISABLE_ID3_METADATA}.
|
||||
*/
|
||||
@Documented
|
||||
@Retention(RetentionPolicy.SOURCE)
|
||||
@ -65,6 +67,7 @@ public final class Mp3Extractor implements Extractor {
|
||||
flag = true,
|
||||
value = {
|
||||
FLAG_ENABLE_CONSTANT_BITRATE_SEEKING,
|
||||
FLAG_ENABLE_CONSTANT_BITRATE_SEEKING_ALWAYS,
|
||||
FLAG_ENABLE_INDEX_SEEKING,
|
||||
FLAG_DISABLE_ID3_METADATA
|
||||
})
|
||||
@ -76,6 +79,21 @@ public final class Mp3Extractor implements Extractor {
|
||||
* <p>This flag is ignored if {@link #FLAG_ENABLE_INDEX_SEEKING} is set.
|
||||
*/
|
||||
public static final int FLAG_ENABLE_CONSTANT_BITRATE_SEEKING = 1;
|
||||
/**
|
||||
* Like {@link #FLAG_ENABLE_CONSTANT_BITRATE_SEEKING}, except that seeking is also enabled in
|
||||
* cases where the content length (and hence the duration of the media) is unknown. Application
|
||||
* code should ensure that requested seek positions are valid when using this flag, or be ready to
|
||||
* handle playback failures reported through {@link Player.Listener#onPlayerError} with {@link
|
||||
* PlaybackException#errorCode} set to {@link
|
||||
* PlaybackException#ERROR_CODE_IO_READ_POSITION_OUT_OF_RANGE}.
|
||||
*
|
||||
* <p>If this flag is set, then the behavior enabled by {@link
|
||||
* #FLAG_ENABLE_CONSTANT_BITRATE_SEEKING} is implicitly enabled.
|
||||
*
|
||||
* <p>This flag is ignored if {@link #FLAG_ENABLE_INDEX_SEEKING} is set.
|
||||
*/
|
||||
public static final int FLAG_ENABLE_CONSTANT_BITRATE_SEEKING_ALWAYS = 1 << 1;
|
||||
|
||||
/**
|
||||
* Flag to force index seeking, in which a time-to-byte mapping is built as the file is read.
|
||||
*
|
||||
@ -88,12 +106,12 @@ public final class Mp3Extractor implements Extractor {
|
||||
* provide precise enough seeking metadata.
|
||||
* </ul>
|
||||
*/
|
||||
public static final int FLAG_ENABLE_INDEX_SEEKING = 1 << 1;
|
||||
public static final int FLAG_ENABLE_INDEX_SEEKING = 1 << 2;
|
||||
/**
|
||||
* Flag to disable parsing of ID3 metadata. Can be set to save memory if ID3 metadata is not
|
||||
* required.
|
||||
*/
|
||||
public static final int FLAG_DISABLE_ID3_METADATA = 1 << 2;
|
||||
public static final int FLAG_DISABLE_ID3_METADATA = 1 << 3;
|
||||
|
||||
/** Predicate that matches ID3 frames containing only required gapless/seeking metadata. */
|
||||
private static final FramePredicate REQUIRED_ID3_FRAME_PREDICATE =
|
||||
@ -158,6 +176,9 @@ public final class Mp3Extractor implements Extractor {
|
||||
* C#TIME_UNSET} if forcing is not required.
|
||||
*/
|
||||
public Mp3Extractor(@Flags int flags, long forcedFirstSampleTimestampUs) {
|
||||
if ((flags & FLAG_ENABLE_CONSTANT_BITRATE_SEEKING_ALWAYS) != 0) {
|
||||
flags |= FLAG_ENABLE_CONSTANT_BITRATE_SEEKING;
|
||||
}
|
||||
this.flags = flags;
|
||||
this.forcedFirstSampleTimestampUs = forcedFirstSampleTimestampUs;
|
||||
scratch = new ParsableByteArray(SCRATCH_LENGTH);
|
||||
@ -446,7 +467,9 @@ public final class Mp3Extractor implements Extractor {
|
||||
|
||||
if (resultSeeker == null
|
||||
|| (!resultSeeker.isSeekable() && (flags & FLAG_ENABLE_CONSTANT_BITRATE_SEEKING) != 0)) {
|
||||
resultSeeker = getConstantBitrateSeeker(input);
|
||||
resultSeeker =
|
||||
getConstantBitrateSeeker(
|
||||
input, (flags & FLAG_ENABLE_CONSTANT_BITRATE_SEEKING_ALWAYS) != 0);
|
||||
}
|
||||
|
||||
return resultSeeker;
|
||||
@ -485,7 +508,7 @@ public final class Mp3Extractor implements Extractor {
|
||||
input.skipFully(synchronizedHeader.frameSize);
|
||||
if (seeker != null && !seeker.isSeekable() && seekHeader == SEEK_HEADER_INFO) {
|
||||
// Fall back to constant bitrate seeking for Info headers missing a table of contents.
|
||||
return getConstantBitrateSeeker(input);
|
||||
return getConstantBitrateSeeker(input, /* allowSeeksIfLengthUnknown= */ false);
|
||||
}
|
||||
} else if (seekHeader == SEEK_HEADER_VBRI) {
|
||||
seeker = VbriSeeker.create(input.getLength(), input.getPosition(), synchronizedHeader, frame);
|
||||
@ -499,11 +522,13 @@ public final class Mp3Extractor implements Extractor {
|
||||
}
|
||||
|
||||
/** Peeks the next frame and returns a {@link ConstantBitrateSeeker} based on its bitrate. */
|
||||
private Seeker getConstantBitrateSeeker(ExtractorInput input) throws IOException {
|
||||
private Seeker getConstantBitrateSeeker(ExtractorInput input, boolean allowSeeksIfLengthUnknown)
|
||||
throws IOException {
|
||||
input.peekFully(scratch.getData(), 0, 4);
|
||||
scratch.setPosition(0);
|
||||
synchronizedHeader.setForHeaderData(scratch.readInt());
|
||||
return new ConstantBitrateSeeker(input.getLength(), input.getPosition(), synchronizedHeader);
|
||||
return new ConstantBitrateSeeker(
|
||||
input.getLength(), input.getPosition(), synchronizedHeader, allowSeeksIfLengthUnknown);
|
||||
}
|
||||
|
||||
@EnsuresNonNull({"extractorOutput", "realTrackOutput"})
|
||||
|
@ -22,6 +22,8 @@ import static com.google.android.exoplayer2.metadata.id3.Id3Decoder.ID3_TAG;
|
||||
import androidx.annotation.IntDef;
|
||||
import com.google.android.exoplayer2.C;
|
||||
import com.google.android.exoplayer2.ParserException;
|
||||
import com.google.android.exoplayer2.PlaybackException;
|
||||
import com.google.android.exoplayer2.Player;
|
||||
import com.google.android.exoplayer2.extractor.ConstantBitrateSeekMap;
|
||||
import com.google.android.exoplayer2.extractor.Extractor;
|
||||
import com.google.android.exoplayer2.extractor.ExtractorInput;
|
||||
@ -48,14 +50,15 @@ public final class AdtsExtractor implements Extractor {
|
||||
public static final ExtractorsFactory FACTORY = () -> new Extractor[] {new AdtsExtractor()};
|
||||
|
||||
/**
|
||||
* Flags controlling the behavior of the extractor. Possible flag value is {@link
|
||||
* #FLAG_ENABLE_CONSTANT_BITRATE_SEEKING}.
|
||||
* Flags controlling the behavior of the extractor. Possible flag values are {@link
|
||||
* #FLAG_ENABLE_CONSTANT_BITRATE_SEEKING} and {@link
|
||||
* #FLAG_ENABLE_CONSTANT_BITRATE_SEEKING_ALWAYS}.
|
||||
*/
|
||||
@Documented
|
||||
@Retention(RetentionPolicy.SOURCE)
|
||||
@IntDef(
|
||||
flag = true,
|
||||
value = {FLAG_ENABLE_CONSTANT_BITRATE_SEEKING})
|
||||
value = {FLAG_ENABLE_CONSTANT_BITRATE_SEEKING, FLAG_ENABLE_CONSTANT_BITRATE_SEEKING_ALWAYS})
|
||||
public @interface Flags {}
|
||||
/**
|
||||
* Flag to force enable seeking using a constant bitrate assumption in cases where seeking would
|
||||
@ -65,6 +68,18 @@ public final class AdtsExtractor implements Extractor {
|
||||
* are not precise, especially when the stream bitrate varies a lot.
|
||||
*/
|
||||
public static final int FLAG_ENABLE_CONSTANT_BITRATE_SEEKING = 1;
|
||||
/**
|
||||
* Like {@link #FLAG_ENABLE_CONSTANT_BITRATE_SEEKING}, except that seeking is also enabled in
|
||||
* cases where the content length (and hence the duration of the media) is unknown. Application
|
||||
* code should ensure that requested seek positions are valid when using this flag, or be ready to
|
||||
* handle playback failures reported through {@link Player.Listener#onPlayerError} with {@link
|
||||
* PlaybackException#errorCode} set to {@link
|
||||
* PlaybackException#ERROR_CODE_IO_READ_POSITION_OUT_OF_RANGE}.
|
||||
*
|
||||
* <p>If this flag is set, then the behavior enabled by {@link
|
||||
* #FLAG_ENABLE_CONSTANT_BITRATE_SEEKING} is implicitly enabled as well.
|
||||
*/
|
||||
public static final int FLAG_ENABLE_CONSTANT_BITRATE_SEEKING_ALWAYS = 1 << 1;
|
||||
|
||||
private static final int MAX_PACKET_SIZE = 2 * 1024;
|
||||
/**
|
||||
@ -105,6 +120,9 @@ public final class AdtsExtractor implements Extractor {
|
||||
* @param flags Flags that control the extractor's behavior.
|
||||
*/
|
||||
public AdtsExtractor(@Flags int flags) {
|
||||
if ((flags & FLAG_ENABLE_CONSTANT_BITRATE_SEEKING_ALWAYS) != 0) {
|
||||
flags |= FLAG_ENABLE_CONSTANT_BITRATE_SEEKING;
|
||||
}
|
||||
this.flags = flags;
|
||||
reader = new AdtsReader(true);
|
||||
packetBuffer = new ParsableByteArray(MAX_PACKET_SIZE);
|
||||
@ -182,14 +200,16 @@ public final class AdtsExtractor implements Extractor {
|
||||
|
||||
long inputLength = input.getLength();
|
||||
boolean canUseConstantBitrateSeeking =
|
||||
(flags & FLAG_ENABLE_CONSTANT_BITRATE_SEEKING) != 0 && inputLength != C.LENGTH_UNSET;
|
||||
(flags & FLAG_ENABLE_CONSTANT_BITRATE_SEEKING_ALWAYS) != 0
|
||||
|| ((flags & FLAG_ENABLE_CONSTANT_BITRATE_SEEKING) != 0
|
||||
&& inputLength != C.LENGTH_UNSET);
|
||||
if (canUseConstantBitrateSeeking) {
|
||||
calculateAverageFrameSize(input);
|
||||
}
|
||||
|
||||
int bytesRead = input.read(packetBuffer.getData(), 0, MAX_PACKET_SIZE);
|
||||
boolean readEndOfStream = bytesRead == RESULT_END_OF_INPUT;
|
||||
maybeOutputSeekMap(inputLength, canUseConstantBitrateSeeking, readEndOfStream);
|
||||
maybeOutputSeekMap(inputLength, readEndOfStream);
|
||||
if (readEndOfStream) {
|
||||
return RESULT_END_OF_INPUT;
|
||||
}
|
||||
@ -231,12 +251,13 @@ public final class AdtsExtractor implements Extractor {
|
||||
}
|
||||
|
||||
@RequiresNonNull("extractorOutput")
|
||||
private void maybeOutputSeekMap(
|
||||
long inputLength, boolean canUseConstantBitrateSeeking, boolean readEndOfStream) {
|
||||
private void maybeOutputSeekMap(long inputLength, boolean readEndOfStream) {
|
||||
if (hasOutputSeekMap) {
|
||||
return;
|
||||
}
|
||||
boolean useConstantBitrateSeeking = canUseConstantBitrateSeeking && averageFrameSize > 0;
|
||||
|
||||
boolean useConstantBitrateSeeking =
|
||||
(flags & FLAG_ENABLE_CONSTANT_BITRATE_SEEKING) != 0 && averageFrameSize > 0;
|
||||
if (useConstantBitrateSeeking
|
||||
&& reader.getSampleDurationUs() == C.TIME_UNSET
|
||||
&& !readEndOfStream) {
|
||||
@ -246,7 +267,9 @@ public final class AdtsExtractor implements Extractor {
|
||||
}
|
||||
|
||||
if (useConstantBitrateSeeking && reader.getSampleDurationUs() != C.TIME_UNSET) {
|
||||
extractorOutput.seekMap(getConstantBitrateSeekMap(inputLength));
|
||||
extractorOutput.seekMap(
|
||||
getConstantBitrateSeekMap(
|
||||
inputLength, (flags & FLAG_ENABLE_CONSTANT_BITRATE_SEEKING_ALWAYS) != 0));
|
||||
} else {
|
||||
extractorOutput.seekMap(new SeekMap.Unseekable(C.TIME_UNSET));
|
||||
}
|
||||
@ -314,9 +337,10 @@ public final class AdtsExtractor implements Extractor {
|
||||
hasCalculatedAverageFrameSize = true;
|
||||
}
|
||||
|
||||
private SeekMap getConstantBitrateSeekMap(long inputLength) {
|
||||
private SeekMap getConstantBitrateSeekMap(long inputLength, boolean allowSeeksIfLengthUnknown) {
|
||||
int bitrate = getBitrateFromFrameSize(averageFrameSize, reader.getSampleDurationUs());
|
||||
return new ConstantBitrateSeekMap(inputLength, firstFramePosition, bitrate, averageFrameSize);
|
||||
return new ConstantBitrateSeekMap(
|
||||
inputLength, firstFramePosition, bitrate, averageFrameSize, allowSeeksIfLengthUnknown);
|
||||
}
|
||||
|
||||
/**
|
||||
|
Loading…
x
Reference in New Issue
Block a user