Decouple ProgressiveMediaPeriod and ExtractorHolder

- Avoid having ExtractorHolder expose the underlying extractor.
- Make ProgressiveMP inject a DataSource instead of a DefaultExtractor.

This CL should introduce no functional changes.

PiperOrigin-RevId: 296944788
This commit is contained in:
aquilescanta 2020-02-24 20:07:31 +00:00 committed by Oliver Woodman
parent af8b8125e5
commit a9eb086678
2 changed files with 72 additions and 28 deletions

View File

@ -15,11 +15,16 @@
*/ */
package com.google.android.exoplayer2.source; package com.google.android.exoplayer2.source;
import android.net.Uri;
import androidx.annotation.Nullable; 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.Extractor;
import com.google.android.exoplayer2.extractor.ExtractorInput; 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.mp3.Mp3Extractor;
import com.google.android.exoplayer2.upstream.DataSource;
import com.google.android.exoplayer2.util.Assertions;
import com.google.android.exoplayer2.util.Util; import com.google.android.exoplayer2.util.Util;
import java.io.EOFException; import java.io.EOFException;
import java.io.IOException; import java.io.IOException;
@ -30,6 +35,7 @@ import java.io.IOException;
private final Extractor[] extractors; private final Extractor[] extractors;
@Nullable private Extractor extractor; @Nullable private Extractor extractor;
@Nullable private ExtractorInput extractorInput;
/** /**
* Creates a holder that will select an extractor and initialize it using the specified output. * 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 * Disables seeking in MP3 streams.
* later calls.
* *
* @param input The {@link ExtractorInput} from which data should be read. * <p>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 * @param output The {@link ExtractorOutput} that will be used to initialize the selected
* extractor. * 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 UnrecognizedInputFormatException Thrown if the input format could not be detected.
* @throws IOException Thrown if the input could not be read. * @throws IOException Thrown if the input could not be read.
* @throws InterruptedException Thrown if the thread was interrupted. * @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 { throws IOException, InterruptedException {
extractorInput = new DefaultExtractorInput(dataSource, position, length);
if (extractor != null) { if (extractor != null) {
return extractor; return;
} }
if (extractors.length == 1) { if (extractors.length == 1) {
this.extractor = extractors[0]; this.extractor = extractors[0];
} else { } else {
for (Extractor extractor : extractors) { for (Extractor extractor : extractors) {
try { try {
if (extractor.sniff(input)) { if (extractor.sniff(extractorInput)) {
this.extractor = extractor; this.extractor = extractor;
break; break;
} }
} catch (EOFException e) { } catch (EOFException e) {
// Do nothing. // Do nothing.
} finally { } finally {
input.resetPeekPosition(); extractorInput.resetPeekPosition();
} }
} }
if (extractor == null) { if (extractor == null) {
@ -78,17 +95,50 @@ import java.io.IOException;
"None of the available extractors (" "None of the available extractors ("
+ Util.getCommaDelimitedSimpleClassNames(extractors) + Util.getCommaDelimitedSimpleClassNames(extractors)
+ ") could read the stream.", + ") could read the stream.",
uri); Assertions.checkNotNull(dataSource.getUri()));
} }
} }
extractor.init(output); 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() { public void release() {
if (extractor != null) { if (extractor != null) {
extractor.release(); extractor.release();
extractor = null; extractor = null;
} }
extractorInput = null;
} }
} }

View File

@ -25,16 +25,13 @@ import com.google.android.exoplayer2.ParserException;
import com.google.android.exoplayer2.SeekParameters; import com.google.android.exoplayer2.SeekParameters;
import com.google.android.exoplayer2.decoder.DecoderInputBuffer; import com.google.android.exoplayer2.decoder.DecoderInputBuffer;
import com.google.android.exoplayer2.drm.DrmSessionManager; 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.Extractor;
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.SeekMap.SeekPoints;
import com.google.android.exoplayer2.extractor.SeekMap.Unseekable; import com.google.android.exoplayer2.extractor.SeekMap.Unseekable;
import com.google.android.exoplayer2.extractor.TrackOutput; 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.Metadata;
import com.google.android.exoplayer2.metadata.icy.IcyHeaders; import com.google.android.exoplayer2.metadata.icy.IcyHeaders;
import com.google.android.exoplayer2.source.MediaSourceEventListener.EventDispatcher; 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 { public void load() throws IOException, InterruptedException {
int result = Extractor.RESULT_CONTINUE; int result = Extractor.RESULT_CONTINUE;
while (result == Extractor.RESULT_CONTINUE && !loadCanceled) { while (result == Extractor.RESULT_CONTINUE && !loadCanceled) {
ExtractorInput input = null;
try { try {
long position = positionHolder.position; long position = positionHolder.position;
dataSpec = buildDataSpec(position); dataSpec = buildDataSpec(position);
@ -963,7 +959,6 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
if (length != C.LENGTH_UNSET) { if (length != C.LENGTH_UNSET) {
length += position; length += position;
} }
Uri uri = Assertions.checkNotNull(dataSource.getUri());
icyHeaders = IcyHeaders.parse(dataSource.getResponseHeaders()); icyHeaders = IcyHeaders.parse(dataSource.getResponseHeaders());
DataSource extractorDataSource = dataSource; DataSource extractorDataSource = dataSource;
if (icyHeaders != null && icyHeaders.metadataInterval != C.LENGTH_UNSET) { if (icyHeaders != null && icyHeaders.metadataInterval != C.LENGTH_UNSET) {
@ -971,23 +966,22 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
icyTrackOutput = icyTrack(); icyTrackOutput = icyTrack();
icyTrackOutput.format(ICY_FORMAT); icyTrackOutput.format(ICY_FORMAT);
} }
input = new DefaultExtractorInput(extractorDataSource, position, length); extractorHolder.init(extractorDataSource, position, length, extractorOutput);
Extractor extractor = extractorHolder.selectExtractor(input, extractorOutput, uri);
// MP3 live streams commonly have seekable metadata, despite being unseekable. if (icyHeaders != null) {
if (icyHeaders != null && extractor instanceof Mp3Extractor) { extractorHolder.disableSeekingOnMp3Streams();
((Mp3Extractor) extractor).disableSeeking();
} }
if (pendingExtractorSeek) { if (pendingExtractorSeek) {
extractor.seek(position, seekTimeUs); extractorHolder.seek(position, seekTimeUs);
pendingExtractorSeek = false; pendingExtractorSeek = false;
} }
while (result == Extractor.RESULT_CONTINUE && !loadCanceled) { while (result == Extractor.RESULT_CONTINUE && !loadCanceled) {
loadCondition.block(); loadCondition.block();
result = extractor.read(input, positionHolder); result = extractorHolder.read(positionHolder);
if (input.getPosition() > position + continueLoadingCheckIntervalBytes) { long currentInputPosition = extractorHolder.getCurrentInputPosition();
position = input.getPosition(); if (currentInputPosition > position + continueLoadingCheckIntervalBytes) {
position = currentInputPosition;
loadCondition.close(); loadCondition.close();
handler.post(onContinueLoadingRequestedRunnable); handler.post(onContinueLoadingRequestedRunnable);
} }
@ -995,8 +989,8 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
} finally { } finally {
if (result == Extractor.RESULT_SEEK) { if (result == Extractor.RESULT_SEEK) {
result = Extractor.RESULT_CONTINUE; result = Extractor.RESULT_CONTINUE;
} else if (input != null) { } else if (extractorHolder.getCurrentInputPosition() != C.POSITION_UNSET) {
positionHolder.position = input.getPosition(); positionHolder.position = extractorHolder.getCurrentInputPosition();
} }
Util.closeQuietly(dataSource); Util.closeQuietly(dataSource);
} }