diff --git a/library/core/src/main/java/com/google/android/exoplayer2/source/ExtractorHolder.java b/library/core/src/main/java/com/google/android/exoplayer2/source/ExtractorHolder.java index 96b1f904d7..205c678798 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/source/ExtractorHolder.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/source/ExtractorHolder.java @@ -15,11 +15,16 @@ */ package com.google.android.exoplayer2.source; -import android.net.Uri; import androidx.annotation.Nullable; +import com.google.android.exoplayer2.C; +import com.google.android.exoplayer2.extractor.DefaultExtractorInput; import com.google.android.exoplayer2.extractor.Extractor; 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.mp3.Mp3Extractor; +import com.google.android.exoplayer2.upstream.DataSource; +import com.google.android.exoplayer2.util.Assertions; import com.google.android.exoplayer2.util.Util; import java.io.EOFException; import java.io.IOException; @@ -30,6 +35,7 @@ import java.io.IOException; private final Extractor[] extractors; @Nullable private Extractor extractor; + @Nullable private ExtractorInput extractorInput; /** * Creates a holder that will select an extractor and initialize it using the specified output. @@ -41,36 +47,47 @@ import java.io.IOException; } /** - * Returns an initialized extractor for reading {@code input}, and returns the same extractor on - * later calls. + * Disables seeking in MP3 streams. * - * @param input The {@link ExtractorInput} from which data should be read. + *

MP3 live streams commonly have seekable metadata, despite being unseekable. + */ + public void disableSeekingOnMp3Streams() { + if (extractor instanceof Mp3Extractor) { + ((Mp3Extractor) extractor).disableSeeking(); + } + } + + /** + * Initializes any necessary resources for extraction. + * + * @param dataSource The {@link DataSource} from which data should be read. + * @param position The initial position of the {@code dataSource} in the stream. + * @param length The length of the stream, or {@link C#LENGTH_UNSET} if it is unknown. * @param output The {@link ExtractorOutput} that will be used to initialize the selected * extractor. - * @param uri The {@link Uri} of the data. - * @return An initialized extractor for reading {@code input}. * @throws UnrecognizedInputFormatException Thrown if the input format could not be detected. * @throws IOException Thrown if the input could not be read. * @throws InterruptedException Thrown if the thread was interrupted. */ - public Extractor selectExtractor(ExtractorInput input, ExtractorOutput output, Uri uri) + public void init(DataSource dataSource, long position, long length, ExtractorOutput output) throws IOException, InterruptedException { + extractorInput = new DefaultExtractorInput(dataSource, position, length); if (extractor != null) { - return extractor; + return; } if (extractors.length == 1) { this.extractor = extractors[0]; } else { for (Extractor extractor : extractors) { try { - if (extractor.sniff(input)) { + if (extractor.sniff(extractorInput)) { this.extractor = extractor; break; } } catch (EOFException e) { // Do nothing. } finally { - input.resetPeekPosition(); + extractorInput.resetPeekPosition(); } } if (extractor == null) { @@ -78,17 +95,50 @@ import java.io.IOException; "None of the available extractors (" + Util.getCommaDelimitedSimpleClassNames(extractors) + ") could read the stream.", - uri); + Assertions.checkNotNull(dataSource.getUri())); } } extractor.init(output); - return extractor; } + /** + * Returns the current read position in the input stream, or {@link C#POSITION_UNSET} if no input + * is available. + */ + public long getCurrentInputPosition() { + return extractorInput != null ? extractorInput.getPosition() : C.POSITION_UNSET; + } + + /** + * Notifies the underlying extractor that a seek has occurred. + * + * @param position The byte offset in the stream from which data will be provided. + * @param seekTimeUs The seek time in microseconds. + */ + public void seek(long position, long seekTimeUs) { + Assertions.checkNotNull(extractor).seek(position, seekTimeUs); + } + + /** + * Extracts data starting at the current input stream position. + * + * @param positionHolder If {@link Extractor#RESULT_SEEK} is returned, this holder is updated to + * hold the position of the required data. + * @return One of the {@link Extractor}{@code .RESULT_*} values. + * @throws IOException If an error occurred reading from the input. + * @throws InterruptedException If the thread was interrupted. + */ + public int read(PositionHolder positionHolder) throws IOException, InterruptedException { + return Assertions.checkNotNull(extractor) + .read(Assertions.checkNotNull(extractorInput), positionHolder); + } + + /** Releases any held resources. */ public void release() { if (extractor != null) { extractor.release(); extractor = null; } + extractorInput = null; } } diff --git a/library/core/src/main/java/com/google/android/exoplayer2/source/ProgressiveMediaPeriod.java b/library/core/src/main/java/com/google/android/exoplayer2/source/ProgressiveMediaPeriod.java index 019ae11052..11be045adc 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/source/ProgressiveMediaPeriod.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/source/ProgressiveMediaPeriod.java @@ -25,16 +25,13 @@ import com.google.android.exoplayer2.ParserException; import com.google.android.exoplayer2.SeekParameters; import com.google.android.exoplayer2.decoder.DecoderInputBuffer; import com.google.android.exoplayer2.drm.DrmSessionManager; -import com.google.android.exoplayer2.extractor.DefaultExtractorInput; import com.google.android.exoplayer2.extractor.Extractor; -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.SeekMap.Unseekable; import com.google.android.exoplayer2.extractor.TrackOutput; -import com.google.android.exoplayer2.extractor.mp3.Mp3Extractor; import com.google.android.exoplayer2.metadata.Metadata; import com.google.android.exoplayer2.metadata.icy.IcyHeaders; import com.google.android.exoplayer2.source.MediaSourceEventListener.EventDispatcher; @@ -955,7 +952,6 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull; public void load() throws IOException, InterruptedException { int result = Extractor.RESULT_CONTINUE; while (result == Extractor.RESULT_CONTINUE && !loadCanceled) { - ExtractorInput input = null; try { long position = positionHolder.position; dataSpec = buildDataSpec(position); @@ -963,7 +959,6 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull; if (length != C.LENGTH_UNSET) { length += position; } - Uri uri = Assertions.checkNotNull(dataSource.getUri()); icyHeaders = IcyHeaders.parse(dataSource.getResponseHeaders()); DataSource extractorDataSource = dataSource; if (icyHeaders != null && icyHeaders.metadataInterval != C.LENGTH_UNSET) { @@ -971,23 +966,22 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull; icyTrackOutput = icyTrack(); icyTrackOutput.format(ICY_FORMAT); } - input = new DefaultExtractorInput(extractorDataSource, position, length); - Extractor extractor = extractorHolder.selectExtractor(input, extractorOutput, uri); + extractorHolder.init(extractorDataSource, position, length, extractorOutput); - // MP3 live streams commonly have seekable metadata, despite being unseekable. - if (icyHeaders != null && extractor instanceof Mp3Extractor) { - ((Mp3Extractor) extractor).disableSeeking(); + if (icyHeaders != null) { + extractorHolder.disableSeekingOnMp3Streams(); } if (pendingExtractorSeek) { - extractor.seek(position, seekTimeUs); + extractorHolder.seek(position, seekTimeUs); pendingExtractorSeek = false; } while (result == Extractor.RESULT_CONTINUE && !loadCanceled) { loadCondition.block(); - result = extractor.read(input, positionHolder); - if (input.getPosition() > position + continueLoadingCheckIntervalBytes) { - position = input.getPosition(); + result = extractorHolder.read(positionHolder); + long currentInputPosition = extractorHolder.getCurrentInputPosition(); + if (currentInputPosition > position + continueLoadingCheckIntervalBytes) { + position = currentInputPosition; loadCondition.close(); handler.post(onContinueLoadingRequestedRunnable); } @@ -995,8 +989,8 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull; } finally { if (result == Extractor.RESULT_SEEK) { result = Extractor.RESULT_CONTINUE; - } else if (input != null) { - positionHolder.position = input.getPosition(); + } else if (extractorHolder.getCurrentInputPosition() != C.POSITION_UNSET) { + positionHolder.position = extractorHolder.getCurrentInputPosition(); } Util.closeQuietly(dataSource); }