Commit 1f17756a by kimvde Committed by Oliver Woodman

Optimize DefaultExtractorsFactory order using MIME types

PiperOrigin-RevId: 315485985
parent c759b5b1
...@@ -174,8 +174,8 @@ ...@@ -174,8 +174,8 @@
* Change the order of extractors for sniffing to reduce start-up latency * Change the order of extractors for sniffing to reduce start-up latency
in `DefaultExtractorsFactory` and `DefaultHlsExtractorsFactory` in `DefaultExtractorsFactory` and `DefaultHlsExtractorsFactory`
([#6410](https://github.com/google/ExoPlayer/issues/6410)). ([#6410](https://github.com/google/ExoPlayer/issues/6410)).
* Select first extractors based on the filename extension in * Select first extractors based on the filename extension and the response
`DefaultExtractorsFactory`. headers mime type in `DefaultExtractorsFactory`.
* Testing * Testing
* Add `TestExoPlayer`, a utility class with APIs to create * Add `TestExoPlayer`, a utility class with APIs to create
`SimpleExoPlayer` instances with fake components for testing. `SimpleExoPlayer` instances with fake components for testing.
......
...@@ -22,6 +22,7 @@ import com.google.android.exoplayer2.extractor.DefaultExtractorInput; ...@@ -22,6 +22,7 @@ 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.ExtractorsFactory;
import com.google.android.exoplayer2.extractor.PositionHolder; import com.google.android.exoplayer2.extractor.PositionHolder;
import com.google.android.exoplayer2.extractor.mp3.Mp3Extractor; import com.google.android.exoplayer2.extractor.mp3.Mp3Extractor;
import com.google.android.exoplayer2.upstream.DataReader; import com.google.android.exoplayer2.upstream.DataReader;
...@@ -29,6 +30,8 @@ import com.google.android.exoplayer2.util.Assertions; ...@@ -29,6 +30,8 @@ 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;
import java.util.List;
import java.util.Map;
/** /**
* {@link ProgressiveMediaExtractor} built on top of {@link Extractor} instances, whose * {@link ProgressiveMediaExtractor} built on top of {@link Extractor} instances, whose
...@@ -36,7 +39,7 @@ import java.io.IOException; ...@@ -36,7 +39,7 @@ import java.io.IOException;
*/ */
/* package */ final class BundledExtractorsAdapter implements ProgressiveMediaExtractor { /* package */ final class BundledExtractorsAdapter implements ProgressiveMediaExtractor {
private final Extractor[] extractors; private final ExtractorsFactory extractorsFactory;
@Nullable private Extractor extractor; @Nullable private Extractor extractor;
@Nullable private ExtractorInput extractorInput; @Nullable private ExtractorInput extractorInput;
...@@ -44,21 +47,27 @@ import java.io.IOException; ...@@ -44,21 +47,27 @@ import java.io.IOException;
/** /**
* 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.
* *
* @param extractors One or more extractors to choose from. * @param extractorsFactory The {@link ExtractorsFactory} providing the extractors to choose from.
*/ */
public BundledExtractorsAdapter(Extractor[] extractors) { public BundledExtractorsAdapter(ExtractorsFactory extractorsFactory) {
this.extractors = extractors; this.extractorsFactory = extractorsFactory;
} }
@Override @Override
public void init( public void init(
DataReader dataReader, Uri uri, long position, long length, ExtractorOutput output) DataReader dataReader,
Uri uri,
Map<String, List<String>> responseHeaders,
long position,
long length,
ExtractorOutput output)
throws IOException { throws IOException {
ExtractorInput extractorInput = new DefaultExtractorInput(dataReader, position, length); ExtractorInput extractorInput = new DefaultExtractorInput(dataReader, position, length);
this.extractorInput = extractorInput; this.extractorInput = extractorInput;
if (extractor != null) { if (extractor != null) {
return; return;
} }
Extractor[] extractors = extractorsFactory.createExtractors(uri, responseHeaders);
if (extractors.length == 1) { if (extractors.length == 1) {
this.extractor = extractors[0]; this.extractor = extractors[0];
} else { } else {
......
...@@ -22,6 +22,8 @@ import com.google.android.exoplayer2.extractor.ExtractorOutput; ...@@ -22,6 +22,8 @@ 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.upstream.DataReader; import com.google.android.exoplayer2.upstream.DataReader;
import java.io.IOException; import java.io.IOException;
import java.util.List;
import java.util.Map;
/** Extracts the contents of a container file from a progressive media stream. */ /** Extracts the contents of a container file from a progressive media stream. */
/* package */ interface ProgressiveMediaExtractor { /* package */ interface ProgressiveMediaExtractor {
...@@ -31,6 +33,7 @@ import java.io.IOException; ...@@ -31,6 +33,7 @@ import java.io.IOException;
* *
* @param dataReader The {@link DataReader} from which data should be read. * @param dataReader The {@link DataReader} from which data should be read.
* @param uri The {@link Uri} from which the media is obtained. * @param uri The {@link Uri} from which the media is obtained.
* @param responseHeaders The response headers of the media, or an empty map if there are none.
* @param position The initial position of the {@code dataReader} in the stream. * @param position The initial position of the {@code dataReader} in the stream.
* @param length The length of the stream, or {@link C#LENGTH_UNSET} if length is unknown. * @param length The length of the stream, or {@link C#LENGTH_UNSET} if length 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
...@@ -38,7 +41,13 @@ import java.io.IOException; ...@@ -38,7 +41,13 @@ import java.io.IOException;
* @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.
*/ */
void init(DataReader dataReader, Uri uri, long position, long length, ExtractorOutput output) void init(
DataReader dataReader,
Uri uri,
Map<String, List<String>> responseHeaders,
long position,
long length,
ExtractorOutput output)
throws IOException; throws IOException;
/** Releases any held resources. */ /** Releases any held resources. */
......
...@@ -27,6 +27,7 @@ import com.google.android.exoplayer2.decoder.DecoderInputBuffer; ...@@ -27,6 +27,7 @@ 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.Extractor; import com.google.android.exoplayer2.extractor.Extractor;
import com.google.android.exoplayer2.extractor.ExtractorOutput; import com.google.android.exoplayer2.extractor.ExtractorOutput;
import com.google.android.exoplayer2.extractor.ExtractorsFactory;
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;
...@@ -143,7 +144,7 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull; ...@@ -143,7 +144,7 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
/** /**
* @param uri The {@link Uri} of the media stream. * @param uri The {@link Uri} of the media stream.
* @param dataSource The data source to read the media. * @param dataSource The data source to read the media.
* @param extractors The extractors to use to read the data source. * @param extractorsFactory The {@link ExtractorsFactory} to use to read the data source.
* @param loadErrorHandlingPolicy The {@link LoadErrorHandlingPolicy}. * @param loadErrorHandlingPolicy The {@link LoadErrorHandlingPolicy}.
* @param eventDispatcher A dispatcher to notify of events. * @param eventDispatcher A dispatcher to notify of events.
* @param listener A listener to notify when information about the period changes. * @param listener A listener to notify when information about the period changes.
...@@ -161,7 +162,7 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull; ...@@ -161,7 +162,7 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
public ProgressiveMediaPeriod( public ProgressiveMediaPeriod(
Uri uri, Uri uri,
DataSource dataSource, DataSource dataSource,
Extractor[] extractors, ExtractorsFactory extractorsFactory,
DrmSessionManager drmSessionManager, DrmSessionManager drmSessionManager,
LoadErrorHandlingPolicy loadErrorHandlingPolicy, LoadErrorHandlingPolicy loadErrorHandlingPolicy,
EventDispatcher eventDispatcher, EventDispatcher eventDispatcher,
...@@ -179,7 +180,8 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull; ...@@ -179,7 +180,8 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
this.customCacheKey = customCacheKey; this.customCacheKey = customCacheKey;
this.continueLoadingCheckIntervalBytes = continueLoadingCheckIntervalBytes; this.continueLoadingCheckIntervalBytes = continueLoadingCheckIntervalBytes;
loader = new Loader("Loader:ProgressiveMediaPeriod"); loader = new Loader("Loader:ProgressiveMediaPeriod");
ProgressiveMediaExtractor progressiveMediaExtractor = new BundledExtractorsAdapter(extractors); ProgressiveMediaExtractor progressiveMediaExtractor =
new BundledExtractorsAdapter(extractorsFactory);
this.progressiveMediaExtractor = progressiveMediaExtractor; this.progressiveMediaExtractor = progressiveMediaExtractor;
loadCondition = new ConditionVariable(); loadCondition = new ConditionVariable();
maybeFinishPrepareRunnable = this::maybeFinishPrepare; maybeFinishPrepareRunnable = this::maybeFinishPrepare;
...@@ -1022,7 +1024,12 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull; ...@@ -1022,7 +1024,12 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
icyTrackOutput.format(ICY_FORMAT); icyTrackOutput.format(ICY_FORMAT);
} }
progressiveMediaExtractor.init( progressiveMediaExtractor.init(
extractorDataSource, uri, position, length, extractorOutput); extractorDataSource,
uri,
dataSource.getResponseHeaders(),
position,
length,
extractorOutput);
if (icyHeaders != null) { if (icyHeaders != null) {
progressiveMediaExtractor.disableSeekingOnMp3Streams(); progressiveMediaExtractor.disableSeekingOnMp3Streams();
......
...@@ -276,7 +276,7 @@ public final class ProgressiveMediaSource extends BaseMediaSource ...@@ -276,7 +276,7 @@ public final class ProgressiveMediaSource extends BaseMediaSource
return new ProgressiveMediaPeriod( return new ProgressiveMediaPeriod(
playbackProperties.uri, playbackProperties.uri,
dataSource, dataSource,
extractorsFactory.createExtractors(playbackProperties.uri), extractorsFactory,
drmSessionManager, drmSessionManager,
loadableLoadErrorHandlingPolicy, loadableLoadErrorHandlingPolicy,
createEventDispatcher(id), createEventDispatcher(id),
......
...@@ -15,6 +15,7 @@ ...@@ -15,6 +15,7 @@
*/ */
package com.google.android.exoplayer2.extractor; package com.google.android.exoplayer2.extractor;
import static com.google.android.exoplayer2.util.FileTypes.inferFileTypeFromResponseHeaders;
import static com.google.android.exoplayer2.util.FileTypes.inferFileTypeFromUri; import static com.google.android.exoplayer2.util.FileTypes.inferFileTypeFromUri;
import android.net.Uri; import android.net.Uri;
...@@ -39,7 +40,9 @@ import com.google.android.exoplayer2.util.FileTypes; ...@@ -39,7 +40,9 @@ import com.google.android.exoplayer2.util.FileTypes;
import com.google.android.exoplayer2.util.TimestampAdjuster; import com.google.android.exoplayer2.util.TimestampAdjuster;
import java.lang.reflect.Constructor; import java.lang.reflect.Constructor;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.HashMap;
import java.util.List; import java.util.List;
import java.util.Map;
/** /**
* An {@link ExtractorsFactory} that provides an array of extractors for the following formats: * An {@link ExtractorsFactory} that provides an array of extractors for the following formats:
...@@ -265,18 +268,28 @@ public final class DefaultExtractorsFactory implements ExtractorsFactory { ...@@ -265,18 +268,28 @@ public final class DefaultExtractorsFactory implements ExtractorsFactory {
@Override @Override
public synchronized Extractor[] createExtractors() { public synchronized Extractor[] createExtractors() {
return createExtractors(Uri.EMPTY); return createExtractors(Uri.EMPTY, new HashMap<>());
} }
@Override @Override
public synchronized Extractor[] createExtractors(Uri uri) { public synchronized Extractor[] createExtractors(
Uri uri, Map<String, List<String>> responseHeaders) {
List<Extractor> extractors = new ArrayList<>(/* initialCapacity= */ 14); List<Extractor> extractors = new ArrayList<>(/* initialCapacity= */ 14);
@FileTypes.Type int inferredFileType = inferFileTypeFromUri(uri); @FileTypes.Type
addExtractorsForFormat(inferredFileType, extractors); int responseHeadersInferredFileType = inferFileTypeFromResponseHeaders(responseHeaders);
if (responseHeadersInferredFileType != FileTypes.UNKNOWN) {
addExtractorsForFormat(responseHeadersInferredFileType, extractors);
}
@FileTypes.Type int uriInferredFileType = inferFileTypeFromUri(uri);
if (uriInferredFileType != FileTypes.UNKNOWN
&& uriInferredFileType != responseHeadersInferredFileType) {
addExtractorsForFormat(uriInferredFileType, extractors);
}
for (int format : DEFAULT_EXTRACTOR_ORDER) { for (int format : DEFAULT_EXTRACTOR_ORDER) {
if (format != inferredFileType) { if (format != responseHeadersInferredFileType && format != uriInferredFileType) {
addExtractorsForFormat(format, extractors); addExtractorsForFormat(format, extractors);
} }
} }
......
...@@ -16,6 +16,8 @@ ...@@ -16,6 +16,8 @@
package com.google.android.exoplayer2.extractor; package com.google.android.exoplayer2.extractor;
import android.net.Uri; import android.net.Uri;
import java.util.List;
import java.util.Map;
/** Factory for arrays of {@link Extractor} instances. */ /** Factory for arrays of {@link Extractor} instances. */
public interface ExtractorsFactory { public interface ExtractorsFactory {
...@@ -24,10 +26,14 @@ public interface ExtractorsFactory { ...@@ -24,10 +26,14 @@ public interface ExtractorsFactory {
Extractor[] createExtractors(); Extractor[] createExtractors();
/** /**
* Returns an array of new {@link Extractor} instances to extract the stream corresponding to the * Returns an array of new {@link Extractor} instances.
* provided {@link Uri}. *
* @param uri The {@link Uri} of the media to extract.
* @param responseHeaders The response headers of the media to extract, or an empty map if there
* are none. The map lookup should be case-insensitive.
* @return The {@link Extractor} instances.
*/ */
default Extractor[] createExtractors(Uri uri) { default Extractor[] createExtractors(Uri uri, Map<String, List<String>> responseHeaders) {
return createExtractors(); return createExtractors();
} }
} }
...@@ -33,8 +33,12 @@ import com.google.android.exoplayer2.extractor.ts.AdtsExtractor; ...@@ -33,8 +33,12 @@ import com.google.android.exoplayer2.extractor.ts.AdtsExtractor;
import com.google.android.exoplayer2.extractor.ts.PsExtractor; import com.google.android.exoplayer2.extractor.ts.PsExtractor;
import com.google.android.exoplayer2.extractor.ts.TsExtractor; import com.google.android.exoplayer2.extractor.ts.TsExtractor;
import com.google.android.exoplayer2.extractor.wav.WavExtractor; import com.google.android.exoplayer2.extractor.wav.WavExtractor;
import com.google.android.exoplayer2.util.MimeTypes;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.List; import java.util.List;
import java.util.Map;
import org.junit.Test; import org.junit.Test;
import org.junit.runner.RunWith; import org.junit.runner.RunWith;
...@@ -43,7 +47,7 @@ import org.junit.runner.RunWith; ...@@ -43,7 +47,7 @@ import org.junit.runner.RunWith;
public final class DefaultExtractorsFactoryTest { public final class DefaultExtractorsFactoryTest {
@Test @Test
public void createExtractors_withoutUri_optimizesSniffingOrder() { public void createExtractors_withoutMediaInfo_optimizesSniffingOrder() {
DefaultExtractorsFactory defaultExtractorsFactory = new DefaultExtractorsFactory(); DefaultExtractorsFactory defaultExtractorsFactory = new DefaultExtractorsFactory();
Extractor[] extractors = defaultExtractorsFactory.createExtractors(); Extractor[] extractors = defaultExtractorsFactory.createExtractors();
...@@ -69,24 +73,31 @@ public final class DefaultExtractorsFactoryTest { ...@@ -69,24 +73,31 @@ public final class DefaultExtractorsFactoryTest {
} }
@Test @Test
public void createExtractors_withUri_startsWithExtractorsMatchingExtension() { public void createExtractors_withMediaInfo_startsWithExtractorsMatchingHeadersAndThenUri() {
DefaultExtractorsFactory defaultExtractorsFactory = new DefaultExtractorsFactory(); DefaultExtractorsFactory defaultExtractorsFactory = new DefaultExtractorsFactory();
Uri uri = Uri.parse("test.mp3");
Map<String, List<String>> responseHeaders = new HashMap<>();
responseHeaders.put("Content-Type", Collections.singletonList(MimeTypes.VIDEO_MP4));
Extractor[] extractors = defaultExtractorsFactory.createExtractors(Uri.parse("test.mp4")); Extractor[] extractors = defaultExtractorsFactory.createExtractors(uri, responseHeaders);
List<Class<? extends Extractor>> extractorClasses = getExtractorClasses(extractors); List<Class<? extends Extractor>> extractorClasses = getExtractorClasses(extractors);
assertThat(extractorClasses.subList(0, 2)) assertThat(extractorClasses.subList(0, 2))
.containsExactly(Mp4Extractor.class, FragmentedMp4Extractor.class); .containsExactly(Mp4Extractor.class, FragmentedMp4Extractor.class);
assertThat(extractorClasses.get(2)).isEqualTo(Mp3Extractor.class);
} }
@Test @Test
public void createExtractors_withUri_optimizesSniffingOrder() { public void createExtractors_withMediaInfo_optimizesSniffingOrder() {
DefaultExtractorsFactory defaultExtractorsFactory = new DefaultExtractorsFactory(); DefaultExtractorsFactory defaultExtractorsFactory = new DefaultExtractorsFactory();
Uri uri = Uri.parse("test.mp3");
Map<String, List<String>> responseHeaders = new HashMap<>();
responseHeaders.put("Content-Type", Collections.singletonList(MimeTypes.VIDEO_MP4));
Extractor[] extractors = defaultExtractorsFactory.createExtractors(Uri.parse("test.mp4")); Extractor[] extractors = defaultExtractorsFactory.createExtractors(uri, responseHeaders);
List<Class<? extends Extractor>> extractorClasses = getExtractorClasses(extractors); List<Class<? extends Extractor>> extractorClasses = getExtractorClasses(extractors);
assertThat(extractorClasses.subList(2, extractors.length)) assertThat(extractorClasses.subList(3, extractors.length))
.containsExactly( .containsExactly(
FlvExtractor.class, FlvExtractor.class,
FlacExtractor.class, FlacExtractor.class,
...@@ -98,8 +109,7 @@ public final class DefaultExtractorsFactoryTest { ...@@ -98,8 +109,7 @@ public final class DefaultExtractorsFactoryTest {
MatroskaExtractor.class, MatroskaExtractor.class,
AdtsExtractor.class, AdtsExtractor.class,
Ac3Extractor.class, Ac3Extractor.class,
Ac4Extractor.class, Ac4Extractor.class)
Mp3Extractor.class)
.inOrder(); .inOrder();
} }
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or sign in to comment