Skip to content

Commit

Permalink
Optimize DefaultExtractorsFactory order using MIME types
Browse files Browse the repository at this point in the history
PiperOrigin-RevId: 315485985
  • Loading branch information
kim-vde authored and ojw28 committed Jun 11, 2020
1 parent c759b5b commit 1f17756
Show file tree
Hide file tree
Showing 8 changed files with 83 additions and 29 deletions.
4 changes: 2 additions & 2 deletions RELEASENOTES.md
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,43 +22,52 @@
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;
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
* implementation classes are bundled in the app.
*/
/* package */ final class BundledExtractorsAdapter implements ProgressiveMediaExtractor {

private final Extractor[] extractors;
private final ExtractorsFactory extractorsFactory;

@Nullable private Extractor extractor;
@Nullable private ExtractorInput extractorInput;

/**
* 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<String, List<String>> 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 {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,8 @@
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 {
Expand All @@ -31,14 +33,21 @@
*
* @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
* extractor.
* @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<String, List<String>> responseHeaders,
long position,
long length,
ExtractorOutput output)
throws IOException;

/** Releases any held resources. */
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@
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;
Expand Down Expand Up @@ -143,7 +144,7 @@ interface Listener {
/**
* @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.
Expand All @@ -161,7 +162,7 @@ interface Listener {
public ProgressiveMediaPeriod(
Uri uri,
DataSource dataSource,
Extractor[] extractors,
ExtractorsFactory extractorsFactory,
DrmSessionManager drmSessionManager,
LoadErrorHandlingPolicy loadErrorHandlingPolicy,
EventDispatcher eventDispatcher,
Expand All @@ -179,7 +180,8 @@ public ProgressiveMediaPeriod(
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;
Expand Down Expand Up @@ -1022,7 +1024,12 @@ public void load() throws IOException {
icyTrackOutput.format(ICY_FORMAT);
}
progressiveMediaExtractor.init(
extractorDataSource, uri, position, length, extractorOutput);
extractorDataSource,
uri,
dataSource.getResponseHeaders(),
position,
length,
extractorOutput);

if (icyHeaders != null) {
progressiveMediaExtractor.disableSeekingOnMp3Streams();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -276,7 +276,7 @@ public MediaPeriod createPeriod(MediaPeriodId id, Allocator allocator, long star
return new ProgressiveMediaPeriod(
playbackProperties.uri,
dataSource,
extractorsFactory.createExtractors(playbackProperties.uri),
extractorsFactory,
drmSessionManager,
loadableLoadErrorHandlingPolicy,
createEventDispatcher(id),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -39,7 +40,9 @@
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:
Expand Down Expand Up @@ -265,18 +268,28 @@ public synchronized DefaultExtractorsFactory setTsExtractorFlags(

@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<String, List<String>> responseHeaders) {
List<Extractor> 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);
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand All @@ -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<String, List<String>> responseHeaders) {
return createExtractors();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -33,8 +33,12 @@
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;

Expand All @@ -43,7 +47,7 @@
public final class DefaultExtractorsFactoryTest {

@Test
public void createExtractors_withoutUri_optimizesSniffingOrder() {
public void createExtractors_withoutMediaInfo_optimizesSniffingOrder() {
DefaultExtractorsFactory defaultExtractorsFactory = new DefaultExtractorsFactory();

Extractor[] extractors = defaultExtractorsFactory.createExtractors();
Expand All @@ -69,24 +73,31 @@ public void createExtractors_withoutUri_optimizesSniffingOrder() {
}

@Test
public void createExtractors_withUri_startsWithExtractorsMatchingExtension() {
public void createExtractors_withMediaInfo_startsWithExtractorsMatchingHeadersAndThenUri() {
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);
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<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);
assertThat(extractorClasses.subList(2, extractors.length))
assertThat(extractorClasses.subList(3, extractors.length))
.containsExactly(
FlvExtractor.class,
FlacExtractor.class,
Expand All @@ -98,8 +109,7 @@ public void createExtractors_withUri_optimizesSniffingOrder() {
MatroskaExtractor.class,
AdtsExtractor.class,
Ac3Extractor.class,
Ac4Extractor.class,
Mp3Extractor.class)
Ac4Extractor.class)
.inOrder();
}

Expand Down

0 comments on commit 1f17756

Please sign in to comment.