From 1f17756ad2cb4f427bac57de8d0911147307cd06 Mon Sep 17 00:00:00 2001 From: kimvde Date: Tue, 9 Jun 2020 16:12:36 +0100 Subject: [PATCH] Optimize DefaultExtractorsFactory order using MIME types PiperOrigin-RevId: 315485985 --- RELEASENOTES.md | 4 +-- .../source/BundledExtractorsAdapter.java | 19 ++++++++++---- .../source/ProgressiveMediaExtractor.java | 11 +++++++- .../source/ProgressiveMediaPeriod.java | 15 ++++++++--- .../source/ProgressiveMediaSource.java | 2 +- .../extractor/DefaultExtractorsFactory.java | 23 ++++++++++++---- .../extractor/ExtractorsFactory.java | 12 ++++++--- .../DefaultExtractorsFactoryTest.java | 26 +++++++++++++------ 8 files changed, 83 insertions(+), 29 deletions(-) diff --git a/RELEASENOTES.md b/RELEASENOTES.md index d19381bf3f..a7ad147963 100644 --- a/RELEASENOTES.md +++ b/RELEASENOTES.md @@ -174,8 +174,8 @@ * Change the order of extractors for sniffing to reduce start-up latency in `DefaultExtractorsFactory` and `DefaultHlsExtractorsFactory` ([#6410](https://github.com/google/ExoPlayer/issues/6410)). - * Select first extractors based on the filename extension in - `DefaultExtractorsFactory`. + * Select first extractors based on the filename extension and the response + headers mime type in `DefaultExtractorsFactory`. * Testing * Add `TestExoPlayer`, a utility class with APIs to create `SimpleExoPlayer` instances with fake components for testing. diff --git a/library/core/src/main/java/com/google/android/exoplayer2/source/BundledExtractorsAdapter.java b/library/core/src/main/java/com/google/android/exoplayer2/source/BundledExtractorsAdapter.java index 4b487bfbdf..7e770d4e39 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/source/BundledExtractorsAdapter.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/source/BundledExtractorsAdapter.java @@ -22,6 +22,7 @@ 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.ExtractorsFactory; import com.google.android.exoplayer2.extractor.PositionHolder; import com.google.android.exoplayer2.extractor.mp3.Mp3Extractor; import com.google.android.exoplayer2.upstream.DataReader; @@ -29,6 +30,8 @@ import com.google.android.exoplayer2.util.Assertions; import com.google.android.exoplayer2.util.Util; import java.io.EOFException; import java.io.IOException; +import java.util.List; +import java.util.Map; /** * {@link ProgressiveMediaExtractor} built on top of {@link Extractor} instances, whose @@ -36,7 +39,7 @@ import java.io.IOException; */ /* package */ final class BundledExtractorsAdapter implements ProgressiveMediaExtractor { - private final Extractor[] extractors; + private final ExtractorsFactory extractorsFactory; @Nullable private Extractor extractor; @Nullable private ExtractorInput extractorInput; @@ -44,21 +47,27 @@ import java.io.IOException; /** * 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) { - this.extractors = extractors; + public BundledExtractorsAdapter(ExtractorsFactory extractorsFactory) { + this.extractorsFactory = extractorsFactory; } @Override public void init( - DataReader dataReader, Uri uri, long position, long length, ExtractorOutput output) + DataReader dataReader, + Uri uri, + Map> responseHeaders, + long position, + long length, + ExtractorOutput output) throws IOException { ExtractorInput extractorInput = new DefaultExtractorInput(dataReader, position, length); this.extractorInput = extractorInput; if (extractor != null) { return; } + Extractor[] extractors = extractorsFactory.createExtractors(uri, responseHeaders); if (extractors.length == 1) { this.extractor = extractors[0]; } else { diff --git a/library/core/src/main/java/com/google/android/exoplayer2/source/ProgressiveMediaExtractor.java b/library/core/src/main/java/com/google/android/exoplayer2/source/ProgressiveMediaExtractor.java index 6cc7c91232..9efe6acba1 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/source/ProgressiveMediaExtractor.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/source/ProgressiveMediaExtractor.java @@ -22,6 +22,8 @@ import com.google.android.exoplayer2.extractor.ExtractorOutput; import com.google.android.exoplayer2.extractor.PositionHolder; import com.google.android.exoplayer2.upstream.DataReader; import java.io.IOException; +import java.util.List; +import java.util.Map; /** Extracts the contents of a container file from a progressive media stream. */ /* package */ interface ProgressiveMediaExtractor { @@ -31,6 +33,7 @@ import java.io.IOException; * * @param dataReader The {@link DataReader} from which data should be read. * @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 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 @@ -38,7 +41,13 @@ import java.io.IOException; * @throws UnrecognizedInputFormatException Thrown if the input format could not be detected. * @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> responseHeaders, + long position, + long length, + ExtractorOutput output) throws IOException; /** Releases any held resources. */ 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 0f5d2ce344..1e48d95bee 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 @@ -27,6 +27,7 @@ import com.google.android.exoplayer2.decoder.DecoderInputBuffer; import com.google.android.exoplayer2.drm.DrmSessionManager; import com.google.android.exoplayer2.extractor.Extractor; 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.SeekMap; import com.google.android.exoplayer2.extractor.SeekMap.SeekPoints; @@ -143,7 +144,7 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull; /** * @param uri The {@link Uri} of the media stream. * @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 eventDispatcher A dispatcher to notify of events. * @param listener A listener to notify when information about the period changes. @@ -161,7 +162,7 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull; public ProgressiveMediaPeriod( Uri uri, DataSource dataSource, - Extractor[] extractors, + ExtractorsFactory extractorsFactory, DrmSessionManager drmSessionManager, LoadErrorHandlingPolicy loadErrorHandlingPolicy, EventDispatcher eventDispatcher, @@ -179,7 +180,8 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull; this.customCacheKey = customCacheKey; this.continueLoadingCheckIntervalBytes = continueLoadingCheckIntervalBytes; loader = new Loader("Loader:ProgressiveMediaPeriod"); - ProgressiveMediaExtractor progressiveMediaExtractor = new BundledExtractorsAdapter(extractors); + ProgressiveMediaExtractor progressiveMediaExtractor = + new BundledExtractorsAdapter(extractorsFactory); this.progressiveMediaExtractor = progressiveMediaExtractor; loadCondition = new ConditionVariable(); maybeFinishPrepareRunnable = this::maybeFinishPrepare; @@ -1022,7 +1024,12 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull; icyTrackOutput.format(ICY_FORMAT); } progressiveMediaExtractor.init( - extractorDataSource, uri, position, length, extractorOutput); + extractorDataSource, + uri, + dataSource.getResponseHeaders(), + position, + length, + extractorOutput); if (icyHeaders != null) { progressiveMediaExtractor.disableSeekingOnMp3Streams(); diff --git a/library/core/src/main/java/com/google/android/exoplayer2/source/ProgressiveMediaSource.java b/library/core/src/main/java/com/google/android/exoplayer2/source/ProgressiveMediaSource.java index fbb657a4e4..bfec295568 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/source/ProgressiveMediaSource.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/source/ProgressiveMediaSource.java @@ -276,7 +276,7 @@ public final class ProgressiveMediaSource extends BaseMediaSource return new ProgressiveMediaPeriod( playbackProperties.uri, dataSource, - extractorsFactory.createExtractors(playbackProperties.uri), + extractorsFactory, drmSessionManager, loadableLoadErrorHandlingPolicy, createEventDispatcher(id), 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 585871635c..0cf017b800 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 @@ -15,6 +15,7 @@ */ 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 android.net.Uri; @@ -39,7 +40,9 @@ import com.google.android.exoplayer2.util.FileTypes; import com.google.android.exoplayer2.util.TimestampAdjuster; import java.lang.reflect.Constructor; import java.util.ArrayList; +import java.util.HashMap; import java.util.List; +import java.util.Map; /** * An {@link ExtractorsFactory} that provides an array of extractors for the following formats: @@ -265,18 +268,28 @@ public final class DefaultExtractorsFactory implements ExtractorsFactory { @Override public synchronized Extractor[] createExtractors() { - return createExtractors(Uri.EMPTY); + return createExtractors(Uri.EMPTY, new HashMap<>()); } @Override - public synchronized Extractor[] createExtractors(Uri uri) { + public synchronized Extractor[] createExtractors( + Uri uri, Map> responseHeaders) { List extractors = new ArrayList<>(/* initialCapacity= */ 14); - @FileTypes.Type int inferredFileType = inferFileTypeFromUri(uri); - addExtractorsForFormat(inferredFileType, extractors); + @FileTypes.Type + 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) { - if (format != inferredFileType) { + if (format != responseHeadersInferredFileType && format != uriInferredFileType) { addExtractorsForFormat(format, extractors); } } diff --git a/library/extractor/src/main/java/com/google/android/exoplayer2/extractor/ExtractorsFactory.java b/library/extractor/src/main/java/com/google/android/exoplayer2/extractor/ExtractorsFactory.java index bbd43d8c5a..d077b1b11e 100644 --- a/library/extractor/src/main/java/com/google/android/exoplayer2/extractor/ExtractorsFactory.java +++ b/library/extractor/src/main/java/com/google/android/exoplayer2/extractor/ExtractorsFactory.java @@ -16,6 +16,8 @@ package com.google.android.exoplayer2.extractor; import android.net.Uri; +import java.util.List; +import java.util.Map; /** Factory for arrays of {@link Extractor} instances. */ public interface ExtractorsFactory { @@ -24,10 +26,14 @@ public interface ExtractorsFactory { Extractor[] createExtractors(); /** - * Returns an array of new {@link Extractor} instances to extract the stream corresponding to the - * provided {@link Uri}. + * Returns an array of new {@link Extractor} instances. + * + * @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> responseHeaders) { return createExtractors(); } } diff --git a/library/extractor/src/test/java/com/google/android/exoplayer2/extractor/DefaultExtractorsFactoryTest.java b/library/extractor/src/test/java/com/google/android/exoplayer2/extractor/DefaultExtractorsFactoryTest.java index 030c45e5b1..ba10f56a51 100644 --- a/library/extractor/src/test/java/com/google/android/exoplayer2/extractor/DefaultExtractorsFactoryTest.java +++ b/library/extractor/src/test/java/com/google/android/exoplayer2/extractor/DefaultExtractorsFactoryTest.java @@ -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.TsExtractor; import com.google.android.exoplayer2.extractor.wav.WavExtractor; +import com.google.android.exoplayer2.util.MimeTypes; import java.util.ArrayList; +import java.util.Collections; +import java.util.HashMap; import java.util.List; +import java.util.Map; import org.junit.Test; import org.junit.runner.RunWith; @@ -43,7 +47,7 @@ import org.junit.runner.RunWith; public final class DefaultExtractorsFactoryTest { @Test - public void createExtractors_withoutUri_optimizesSniffingOrder() { + public void createExtractors_withoutMediaInfo_optimizesSniffingOrder() { DefaultExtractorsFactory defaultExtractorsFactory = new DefaultExtractorsFactory(); Extractor[] extractors = defaultExtractorsFactory.createExtractors(); @@ -69,24 +73,31 @@ public final class DefaultExtractorsFactoryTest { } @Test - public void createExtractors_withUri_startsWithExtractorsMatchingExtension() { + public void createExtractors_withMediaInfo_startsWithExtractorsMatchingHeadersAndThenUri() { DefaultExtractorsFactory defaultExtractorsFactory = new DefaultExtractorsFactory(); + Uri uri = Uri.parse("test.mp3"); + Map> 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> extractorClasses = getExtractorClasses(extractors); assertThat(extractorClasses.subList(0, 2)) .containsExactly(Mp4Extractor.class, FragmentedMp4Extractor.class); + assertThat(extractorClasses.get(2)).isEqualTo(Mp3Extractor.class); } @Test - public void createExtractors_withUri_optimizesSniffingOrder() { + public void createExtractors_withMediaInfo_optimizesSniffingOrder() { DefaultExtractorsFactory defaultExtractorsFactory = new DefaultExtractorsFactory(); + Uri uri = Uri.parse("test.mp3"); + Map> 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> extractorClasses = getExtractorClasses(extractors); - assertThat(extractorClasses.subList(2, extractors.length)) + assertThat(extractorClasses.subList(3, extractors.length)) .containsExactly( FlvExtractor.class, FlacExtractor.class, @@ -98,8 +109,7 @@ public final class DefaultExtractorsFactoryTest { MatroskaExtractor.class, AdtsExtractor.class, Ac3Extractor.class, - Ac4Extractor.class, - Mp3Extractor.class) + Ac4Extractor.class) .inOrder(); }