diff --git a/library/extractor/src/main/java/com/google/android/exoplayer2/extractor/ConstantBitrateSeekMap.java b/library/extractor/src/main/java/com/google/android/exoplayer2/extractor/ConstantBitrateSeekMap.java index 4db7edf685..5e2b743442 100644 --- a/library/extractor/src/main/java/com/google/android/exoplayer2/extractor/ConstantBitrateSeekMap.java +++ b/library/extractor/src/main/java/com/google/android/exoplayer2/extractor/ConstantBitrateSeekMap.java @@ -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; } } diff --git a/library/extractor/src/main/java/com/google/android/exoplayer2/extractor/DefaultExtractorsFactory.java b/library/extractor/src/main/java/com/google/android/exoplayer2/extractor/DefaultExtractorsFactory.java index 0cbde6ab86..abaefe2547 100644 --- a/library/extractor/src/main/java/com/google/android/exoplayer2/extractor/DefaultExtractorsFactory.java +++ b/library/extractor/src/main/java/com/google/android/exoplayer2/extractor/DefaultExtractorsFactory.java @@ -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. + * + *

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: diff --git a/library/extractor/src/main/java/com/google/android/exoplayer2/extractor/amr/AmrExtractor.java b/library/extractor/src/main/java/com/google/android/exoplayer2/extractor/amr/AmrExtractor.java index 92821daf13..2ba8b0d599 100644 --- a/library/extractor/src/main/java/com/google/android/exoplayer2/extractor/amr/AmrExtractor.java +++ b/library/extractor/src/main/java/com/google/android/exoplayer2/extractor/amr/AmrExtractor.java @@ -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}. + * + *

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"}) diff --git a/library/extractor/src/main/java/com/google/android/exoplayer2/extractor/mp3/ConstantBitrateSeeker.java b/library/extractor/src/main/java/com/google/android/exoplayer2/extractor/mp3/ConstantBitrateSeeker.java index f74c3fbc33..9762f2d0e5 100644 --- a/library/extractor/src/main/java/com/google/android/exoplayer2/extractor/mp3/ConstantBitrateSeeker.java +++ b/library/extractor/src/main/java/com/google/android/exoplayer2/extractor/mp3/ConstantBitrateSeeker.java @@ -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 diff --git a/library/extractor/src/main/java/com/google/android/exoplayer2/extractor/mp3/Mp3Extractor.java b/library/extractor/src/main/java/com/google/android/exoplayer2/extractor/mp3/Mp3Extractor.java index 468e31e30a..4e22637144 100644 --- a/library/extractor/src/main/java/com/google/android/exoplayer2/extractor/mp3/Mp3Extractor.java +++ b/library/extractor/src/main/java/com/google/android/exoplayer2/extractor/mp3/Mp3Extractor.java @@ -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 { *

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}. + * + *

If this flag is set, then the behavior enabled by {@link + * #FLAG_ENABLE_CONSTANT_BITRATE_SEEKING} is implicitly enabled. + * + *

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. * */ - 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"}) diff --git a/library/extractor/src/main/java/com/google/android/exoplayer2/extractor/ts/AdtsExtractor.java b/library/extractor/src/main/java/com/google/android/exoplayer2/extractor/ts/AdtsExtractor.java index f4fc317fdc..cccef4ef08 100644 --- a/library/extractor/src/main/java/com/google/android/exoplayer2/extractor/ts/AdtsExtractor.java +++ b/library/extractor/src/main/java/com/google/android/exoplayer2/extractor/ts/AdtsExtractor.java @@ -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}. + * + *

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); } /**