mirror of
https://github.com/samsonjs/media.git
synced 2026-03-30 10:15:48 +00:00
Optimize DefaultExtractorsFactory order using MIME types
PiperOrigin-RevId: 315485985
This commit is contained in:
parent
c759b5b1a9
commit
1f17756ad2
8 changed files with 83 additions and 29 deletions
|
|
@ -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.
|
||||
|
|
|
|||
|
|
@ -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<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 {
|
||||
|
|
|
|||
|
|
@ -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<String, List<String>> responseHeaders,
|
||||
long position,
|
||||
long length,
|
||||
ExtractorOutput output)
|
||||
throws IOException;
|
||||
|
||||
/** Releases any held resources. */
|
||||
|
|
|
|||
|
|
@ -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();
|
||||
|
|
|
|||
|
|
@ -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),
|
||||
|
|
|
|||
|
|
@ -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<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);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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<String, List<String>> responseHeaders) {
|
||||
return createExtractors();
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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<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,
|
||||
|
|
@ -98,8 +109,7 @@ public final class DefaultExtractorsFactoryTest {
|
|||
MatroskaExtractor.class,
|
||||
AdtsExtractor.class,
|
||||
Ac3Extractor.class,
|
||||
Ac4Extractor.class,
|
||||
Mp3Extractor.class)
|
||||
Ac4Extractor.class)
|
||||
.inOrder();
|
||||
}
|
||||
|
||||
|
|
|
|||
Loading…
Reference in a new issue