Commit 88b36abc by kimvde Committed by Oliver Woodman

Sniff all inferred extractors in DefaultHlsExtractorFactory

PiperOrigin-RevId: 315857270
parent 2fcd759e
......@@ -279,26 +279,26 @@ public final class DefaultExtractorsFactory implements ExtractorsFactory {
@FileTypes.Type
int responseHeadersInferredFileType = inferFileTypeFromResponseHeaders(responseHeaders);
if (responseHeadersInferredFileType != FileTypes.UNKNOWN) {
addExtractorsForFormat(responseHeadersInferredFileType, extractors);
addExtractorsForFileType(responseHeadersInferredFileType, extractors);
}
@FileTypes.Type int uriInferredFileType = inferFileTypeFromUri(uri);
if (uriInferredFileType != FileTypes.UNKNOWN
&& uriInferredFileType != responseHeadersInferredFileType) {
addExtractorsForFormat(uriInferredFileType, extractors);
addExtractorsForFileType(uriInferredFileType, extractors);
}
for (int format : DEFAULT_EXTRACTOR_ORDER) {
if (format != responseHeadersInferredFileType && format != uriInferredFileType) {
addExtractorsForFormat(format, extractors);
for (int fileType : DEFAULT_EXTRACTOR_ORDER) {
if (fileType != responseHeadersInferredFileType && fileType != uriInferredFileType) {
addExtractorsForFileType(fileType, extractors);
}
}
return extractors.toArray(new Extractor[extractors.size()]);
}
private void addExtractorsForFormat(@FileTypes.Type int fileFormat, List<Extractor> extractors) {
switch (fileFormat) {
private void addExtractorsForFileType(@FileTypes.Type int fileType, List<Extractor> extractors) {
switch (fileType) {
case FileTypes.AC3:
extractors.add(new Ac3Extractor());
break;
......
......@@ -15,7 +15,7 @@
*/
package com.google.android.exoplayer2.source.hls;
import static com.google.android.exoplayer2.util.FileTypes.inferFileTypeFromUri;
import static com.google.android.exoplayer2.util.Assertions.checkNotNull;
import android.net.Uri;
import android.text.TextUtils;
......@@ -31,12 +31,12 @@ import com.google.android.exoplayer2.extractor.ts.AdtsExtractor;
import com.google.android.exoplayer2.extractor.ts.DefaultTsPayloadReaderFactory;
import com.google.android.exoplayer2.extractor.ts.TsExtractor;
import com.google.android.exoplayer2.metadata.Metadata;
import com.google.android.exoplayer2.util.Assertions;
import com.google.android.exoplayer2.util.FileTypes;
import com.google.android.exoplayer2.util.MimeTypes;
import com.google.android.exoplayer2.util.TimestampAdjuster;
import java.io.EOFException;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Map;
......@@ -46,6 +46,19 @@ import java.util.Map;
*/
public final class DefaultHlsExtractorFactory implements HlsExtractorFactory {
// Extractors order is optimized according to
// https://docs.google.com/document/d/1w2mKaWMxfz2Ei8-LdxqbPs1VLe_oudB-eryXXw9OvQQ.
private static final int[] DEFAULT_EXTRACTOR_ORDER =
new int[] {
FileTypes.MP4,
FileTypes.WEBVTT,
FileTypes.TS,
FileTypes.ADTS,
FileTypes.AC3,
FileTypes.AC4,
FileTypes.MP3,
};
@DefaultTsPayloadReaderFactory.Flags private final int payloadReaderFactoryFlags;
private final boolean exposeCea608WhenMissingDeclarations;
......@@ -90,6 +103,7 @@ public final class DefaultHlsExtractorFactory implements HlsExtractorFactory {
if (isReusable(previousExtractor)) {
return buildResult(previousExtractor);
} else {
@Nullable
Result result =
buildResultForSameExtractorType(previousExtractor, format, timestampAdjuster);
if (result == null) {
......@@ -99,100 +113,56 @@ public final class DefaultHlsExtractorFactory implements HlsExtractorFactory {
}
}
// Try selecting the extractor by the file extension.
@Nullable
Extractor inferredExtractor =
createInferredExtractor(
uri, format, muxedCaptionFormats, timestampAdjuster, responseHeaders);
extractorInput.resetPeekPosition();
if (inferredExtractor != null && sniffQuietly(inferredExtractor, extractorInput)) {
return buildResult(inferredExtractor);
}
// We need to manually sniff each known type, without retrying the one selected by file
// extension. Extractors order is optimized according to
// https://docs.google.com/document/d/1w2mKaWMxfz2Ei8-LdxqbPs1VLe_oudB-eryXXw9OvQQ.
// Extractor to be used if the type is not recognized.
@Nullable Extractor fallBackExtractor = inferredExtractor;
if (!(inferredExtractor instanceof FragmentedMp4Extractor)) {
FragmentedMp4Extractor fragmentedMp4Extractor =
createFragmentedMp4Extractor(timestampAdjuster, format, muxedCaptionFormats);
if (sniffQuietly(fragmentedMp4Extractor, extractorInput)) {
return buildResult(fragmentedMp4Extractor);
}
}
if (!(inferredExtractor instanceof WebvttExtractor)) {
WebvttExtractor webvttExtractor = new WebvttExtractor(format.language, timestampAdjuster);
if (sniffQuietly(webvttExtractor, extractorInput)) {
return buildResult(webvttExtractor);
}
}
@FileTypes.Type
int formatInferredFileType = FileTypes.inferFileTypeFromMimeType(format.sampleMimeType);
@FileTypes.Type
int responseHeadersInferredFileType =
FileTypes.inferFileTypeFromResponseHeaders(responseHeaders);
@FileTypes.Type int uriInferredFileType = FileTypes.inferFileTypeFromUri(uri);
if (!(inferredExtractor instanceof TsExtractor)) {
TsExtractor tsExtractor =
createTsExtractor(
payloadReaderFactoryFlags,
exposeCea608WhenMissingDeclarations,
format,
muxedCaptionFormats,
timestampAdjuster);
if (sniffQuietly(tsExtractor, extractorInput)) {
return buildResult(tsExtractor);
}
if (fallBackExtractor == null) {
fallBackExtractor = tsExtractor;
}
// Defines the order in which to try the extractors.
List<Integer> fileTypeOrder =
new ArrayList<>(/* initialCapacity= */ DEFAULT_EXTRACTOR_ORDER.length);
addFileTypeIfNotPresent(formatInferredFileType, fileTypeOrder);
addFileTypeIfNotPresent(responseHeadersInferredFileType, fileTypeOrder);
addFileTypeIfNotPresent(uriInferredFileType, fileTypeOrder);
for (int fileType : DEFAULT_EXTRACTOR_ORDER) {
addFileTypeIfNotPresent(fileType, fileTypeOrder);
}
if (!(inferredExtractor instanceof AdtsExtractor)) {
AdtsExtractor adtsExtractor = new AdtsExtractor();
if (sniffQuietly(adtsExtractor, extractorInput)) {
return buildResult(adtsExtractor);
// Extractor to be used if the type is not recognized.
@Nullable Extractor fallBackExtractor = null;
extractorInput.resetPeekPosition();
for (int i = 0; i < fileTypeOrder.size(); i++) {
int fileType = fileTypeOrder.get(i);
Extractor extractor =
checkNotNull(
createExtractorByFileType(fileType, format, muxedCaptionFormats, timestampAdjuster));
if (sniffQuietly(extractor, extractorInput)) {
return buildResult(extractor);
}
}
if (!(inferredExtractor instanceof Ac3Extractor)) {
Ac3Extractor ac3Extractor = new Ac3Extractor();
if (sniffQuietly(ac3Extractor, extractorInput)) {
return buildResult(ac3Extractor);
if (fileType == FileTypes.TS) {
fallBackExtractor = extractor;
}
}
if (!(inferredExtractor instanceof Ac4Extractor)) {
Ac4Extractor ac4Extractor = new Ac4Extractor();
if (sniffQuietly(ac4Extractor, extractorInput)) {
return buildResult(ac4Extractor);
}
}
return buildResult(checkNotNull(fallBackExtractor));
}
if (!(inferredExtractor instanceof Mp3Extractor)) {
Mp3Extractor mp3Extractor =
new Mp3Extractor(/* flags= */ 0, /* forcedFirstSampleTimestampUs= */ 0);
if (sniffQuietly(mp3Extractor, extractorInput)) {
return buildResult(mp3Extractor);
}
private static void addFileTypeIfNotPresent(
@FileTypes.Type int fileType, List<Integer> fileTypes) {
if (fileType == FileTypes.UNKNOWN || fileTypes.contains(fileType)) {
return;
}
return buildResult(Assertions.checkNotNull(fallBackExtractor));
fileTypes.add(fileType);
}
@Nullable
private Extractor createInferredExtractor(
Uri uri,
private Extractor createExtractorByFileType(
@FileTypes.Type int fileType,
Format format,
@Nullable List<Format> muxedCaptionFormats,
TimestampAdjuster timestampAdjuster,
Map<String, List<String>> responseHeaders) {
@FileTypes.Type int fileType = FileTypes.inferFileTypeFromMimeType(format.sampleMimeType);
if (fileType == FileTypes.UNKNOWN) {
fileType = FileTypes.inferFileTypeFromResponseHeaders(responseHeaders);
}
if (fileType == FileTypes.UNKNOWN) {
fileType = inferFileTypeFromUri(uri);
}
TimestampAdjuster timestampAdjuster) {
switch (fileType) {
case FileTypes.WEBVTT:
return new WebvttExtractor(format.language, timestampAdjuster);
......
/*
* Copyright 2020 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.google.android.exoplayer2.source.hls;
import static com.google.common.truth.Truth.assertThat;
import android.net.Uri;
import androidx.test.core.app.ApplicationProvider;
import androidx.test.ext.junit.runners.AndroidJUnit4;
import com.google.android.exoplayer2.Format;
import com.google.android.exoplayer2.extractor.DefaultExtractorsFactory;
import com.google.android.exoplayer2.extractor.Extractor;
import com.google.android.exoplayer2.extractor.ExtractorInput;
import com.google.android.exoplayer2.extractor.mp3.Mp3Extractor;
import com.google.android.exoplayer2.extractor.mp4.FragmentedMp4Extractor;
import com.google.android.exoplayer2.extractor.ts.Ac3Extractor;
import com.google.android.exoplayer2.extractor.ts.TsExtractor;
import com.google.android.exoplayer2.testutil.FakeExtractorInput;
import com.google.android.exoplayer2.testutil.TestUtil;
import com.google.android.exoplayer2.util.MimeTypes;
import com.google.android.exoplayer2.util.TimestampAdjuster;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
/** Unit test for {@link DefaultExtractorsFactory}. */
@RunWith(AndroidJUnit4.class)
public class DefaultHlsExtractorFactoryTest {
private Extractor fMp4Extractor;
private Uri tsUri;
private Format webVttFormat;
private TimestampAdjuster timestampAdjuster;
private Map<String, List<String>> ac3ResponseHeaders;
@Before
public void setUp() {
fMp4Extractor = new FragmentedMp4Extractor();
tsUri = Uri.parse("http://path/filename.ts");
webVttFormat = new Format.Builder().setSampleMimeType(MimeTypes.TEXT_VTT).build();
timestampAdjuster = new TimestampAdjuster(/* firstSampleTimestampUs= */ 0);
ac3ResponseHeaders = new HashMap<>();
ac3ResponseHeaders.put("Content-Type", Collections.singletonList(MimeTypes.AUDIO_AC3));
}
@Test
public void createExtractor_withPreviousExtractor_returnsSameExtractorType() throws Exception {
ExtractorInput extractorInput = new FakeExtractorInput.Builder().build();
HlsExtractorFactory.Result result =
new DefaultHlsExtractorFactory()
.createExtractor(
/* previousExtractor= */ fMp4Extractor,
tsUri,
webVttFormat,
/* muxedCaptionFormats= */ null,
timestampAdjuster,
ac3ResponseHeaders,
extractorInput);
assertThat(result.extractor.getClass()).isEqualTo(FragmentedMp4Extractor.class);
}
@Test
public void createExtractor_withFileTypeInFormat_returnsExtractorMatchingFormat()
throws Exception {
ExtractorInput webVttExtractorInput =
new FakeExtractorInput.Builder()
.setData(
TestUtil.getByteArray(
ApplicationProvider.getApplicationContext(), "webvtt/typical"))
.build();
HlsExtractorFactory.Result result =
new DefaultHlsExtractorFactory()
.createExtractor(
/* previousExtractor= */ null,
tsUri,
webVttFormat,
/* muxedCaptionFormats= */ null,
timestampAdjuster,
ac3ResponseHeaders,
webVttExtractorInput);
assertThat(result.extractor.getClass()).isEqualTo(WebvttExtractor.class);
}
@Test
public void
createExtractor_withFileTypeInResponseHeaders_returnsExtractorMatchingResponseHeaders()
throws Exception {
ExtractorInput ac3ExtractorInput =
new FakeExtractorInput.Builder()
.setData(
TestUtil.getByteArray(ApplicationProvider.getApplicationContext(), "ts/sample.ac3"))
.build();
HlsExtractorFactory.Result result =
new DefaultHlsExtractorFactory()
.createExtractor(
/* previousExtractor= */ null,
tsUri,
webVttFormat,
/* muxedCaptionFormats= */ null,
timestampAdjuster,
ac3ResponseHeaders,
ac3ExtractorInput);
assertThat(result.extractor.getClass()).isEqualTo(Ac3Extractor.class);
}
@Test
public void createExtractor_withFileTypeInUri_returnsExtractorMatchingUri() throws Exception {
ExtractorInput tsExtractorInput =
new FakeExtractorInput.Builder()
.setData(
TestUtil.getByteArray(
ApplicationProvider.getApplicationContext(), "ts/sample_ac3.ts"))
.build();
HlsExtractorFactory.Result result =
new DefaultHlsExtractorFactory()
.createExtractor(
/* previousExtractor= */ null,
tsUri,
webVttFormat,
/* muxedCaptionFormats= */ null,
timestampAdjuster,
ac3ResponseHeaders,
tsExtractorInput);
assertThat(result.extractor.getClass()).isEqualTo(TsExtractor.class);
}
@Test
public void createExtractor_withFileTypeNotInMediaInfo_returnsExpectedExtractor()
throws Exception {
ExtractorInput mp3ExtractorInput =
new FakeExtractorInput.Builder()
.setData(
TestUtil.getByteArray(
ApplicationProvider.getApplicationContext(), "mp3/bear-id3.mp3"))
.build();
HlsExtractorFactory.Result result =
new DefaultHlsExtractorFactory()
.createExtractor(
/* previousExtractor= */ null,
tsUri,
webVttFormat,
/* muxedCaptionFormats= */ null,
timestampAdjuster,
ac3ResponseHeaders,
mp3ExtractorInput);
assertThat(result.extractor.getClass()).isEqualTo(Mp3Extractor.class);
}
@Test
public void createExtractor_withNoMatchingExtractor_fallsBackOnTsExtractor() throws Exception {
ExtractorInput emptyExtractorInput = new FakeExtractorInput.Builder().build();
HlsExtractorFactory.Result result =
new DefaultHlsExtractorFactory()
.createExtractor(
/* previousExtractor= */ null,
tsUri,
webVttFormat,
/* muxedCaptionFormats= */ null,
timestampAdjuster,
ac3ResponseHeaders,
emptyExtractorInput);
assertThat(result.extractor.getClass()).isEqualTo(TsExtractor.class);
}
}
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