Fallback to inferred file types when sniffing fails

If none of the extractors successfully sniff the content then we will fall back
to inferred file types in the following order:
- Webvtt if the media comes from a SUBTITLE EXT-X-MEDIA.
- The type of media declared in the HTTP "Content-Type" header.
- The type of the media according to the file extension.
- Transport stream.

Issue: #8700
PiperOrigin-RevId: 362519769
This commit is contained in:
aquilescanta 2021-03-12 15:49:43 +00:00 committed by marcbaechinger
parent 3dae045487
commit 21326e67e3
2 changed files with 74 additions and 11 deletions

View File

@ -125,9 +125,13 @@ public final class DefaultHlsExtractorFactory implements HlsExtractorFactory {
if (sniffQuietly(extractor, extractorInput)) { if (sniffQuietly(extractor, extractorInput)) {
return new BundledHlsMediaChunkExtractor(extractor, format, timestampAdjuster); return new BundledHlsMediaChunkExtractor(extractor, format, timestampAdjuster);
} }
if (fileType == FileTypes.TS) { if (fallBackExtractor == null
// Fall back on TsExtractor to handle TS streams with an EXT-X-MAP tag. See && (fileType == formatInferredFileType
// https://github.com/google/ExoPlayer/issues/8219. || fileType == responseHeadersInferredFileType
|| fileType == uriInferredFileType
|| fileType == FileTypes.TS)) {
// If sniffing fails, fallback to the file types inferred from context. If all else fails,
// fallback to Transport Stream. See https://github.com/google/ExoPlayer/issues/8219.
fallBackExtractor = extractor; fallBackExtractor = extractor;
} }
} }

View File

@ -24,12 +24,14 @@ import com.google.android.exoplayer2.Format;
import com.google.android.exoplayer2.extractor.DefaultExtractorsFactory; import com.google.android.exoplayer2.extractor.DefaultExtractorsFactory;
import com.google.android.exoplayer2.extractor.ExtractorInput; import com.google.android.exoplayer2.extractor.ExtractorInput;
import com.google.android.exoplayer2.extractor.mp3.Mp3Extractor; 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.Ac3Extractor;
import com.google.android.exoplayer2.extractor.ts.TsExtractor; import com.google.android.exoplayer2.extractor.ts.TsExtractor;
import com.google.android.exoplayer2.testutil.FakeExtractorInput; import com.google.android.exoplayer2.testutil.FakeExtractorInput;
import com.google.android.exoplayer2.testutil.TestUtil; import com.google.android.exoplayer2.testutil.TestUtil;
import com.google.android.exoplayer2.util.MimeTypes; import com.google.android.exoplayer2.util.MimeTypes;
import com.google.android.exoplayer2.util.TimestampAdjuster; import com.google.android.exoplayer2.util.TimestampAdjuster;
import com.google.common.collect.ImmutableMap;
import java.util.Collections; import java.util.Collections;
import java.util.HashMap; import java.util.HashMap;
import java.util.List; import java.util.List;
@ -42,14 +44,15 @@ import org.junit.runner.RunWith;
@RunWith(AndroidJUnit4.class) @RunWith(AndroidJUnit4.class)
public class DefaultHlsExtractorFactoryTest { public class DefaultHlsExtractorFactoryTest {
private Uri tsUri; private static final Uri URI_WITH_TS_EXTENSION = Uri.parse("http://path/filename.ts");
private static final Uri URI_WITH_MP4_EXTENSION = Uri.parse("http://path/filename.mp4");
private Format webVttFormat; private Format webVttFormat;
private TimestampAdjuster timestampAdjuster; private TimestampAdjuster timestampAdjuster;
private Map<String, List<String>> ac3ResponseHeaders; private Map<String, List<String>> ac3ResponseHeaders;
@Before @Before
public void setUp() { public void setUp() {
tsUri = Uri.parse("http://path/filename.ts");
webVttFormat = new Format.Builder().setSampleMimeType(MimeTypes.TEXT_VTT).build(); webVttFormat = new Format.Builder().setSampleMimeType(MimeTypes.TEXT_VTT).build();
timestampAdjuster = new TimestampAdjuster(/* firstSampleTimestampUs= */ 0); timestampAdjuster = new TimestampAdjuster(/* firstSampleTimestampUs= */ 0);
ac3ResponseHeaders = new HashMap<>(); ac3ResponseHeaders = new HashMap<>();
@ -69,7 +72,7 @@ public class DefaultHlsExtractorFactoryTest {
BundledHlsMediaChunkExtractor result = BundledHlsMediaChunkExtractor result =
new DefaultHlsExtractorFactory() new DefaultHlsExtractorFactory()
.createExtractor( .createExtractor(
tsUri, URI_WITH_TS_EXTENSION,
webVttFormat, webVttFormat,
/* muxedCaptionFormats= */ null, /* muxedCaptionFormats= */ null,
timestampAdjuster, timestampAdjuster,
@ -93,7 +96,7 @@ public class DefaultHlsExtractorFactoryTest {
BundledHlsMediaChunkExtractor result = BundledHlsMediaChunkExtractor result =
new DefaultHlsExtractorFactory() new DefaultHlsExtractorFactory()
.createExtractor( .createExtractor(
tsUri, URI_WITH_TS_EXTENSION,
webVttFormat, webVttFormat,
/* muxedCaptionFormats= */ null, /* muxedCaptionFormats= */ null,
timestampAdjuster, timestampAdjuster,
@ -115,7 +118,7 @@ public class DefaultHlsExtractorFactoryTest {
BundledHlsMediaChunkExtractor result = BundledHlsMediaChunkExtractor result =
new DefaultHlsExtractorFactory() new DefaultHlsExtractorFactory()
.createExtractor( .createExtractor(
tsUri, URI_WITH_TS_EXTENSION,
webVttFormat, webVttFormat,
/* muxedCaptionFormats= */ null, /* muxedCaptionFormats= */ null,
timestampAdjuster, timestampAdjuster,
@ -138,7 +141,7 @@ public class DefaultHlsExtractorFactoryTest {
BundledHlsMediaChunkExtractor result = BundledHlsMediaChunkExtractor result =
new DefaultHlsExtractorFactory() new DefaultHlsExtractorFactory()
.createExtractor( .createExtractor(
tsUri, URI_WITH_TS_EXTENSION,
webVttFormat, webVttFormat,
/* muxedCaptionFormats= */ null, /* muxedCaptionFormats= */ null,
timestampAdjuster, timestampAdjuster,
@ -149,19 +152,75 @@ public class DefaultHlsExtractorFactoryTest {
} }
@Test @Test
public void createExtractor_withNoMatchingExtractor_fallsBackOnTsExtractor() throws Exception { public void createExtractor_onFailedSniff_fallsBackOnFormatInferred() throws Exception {
ExtractorInput emptyExtractorInput = new FakeExtractorInput.Builder().build(); ExtractorInput emptyExtractorInput = new FakeExtractorInput.Builder().build();
BundledHlsMediaChunkExtractor result = BundledHlsMediaChunkExtractor result =
new DefaultHlsExtractorFactory() new DefaultHlsExtractorFactory()
.createExtractor( .createExtractor(
tsUri, URI_WITH_MP4_EXTENSION,
webVttFormat, webVttFormat,
/* muxedCaptionFormats= */ null, /* muxedCaptionFormats= */ null,
timestampAdjuster, timestampAdjuster,
ac3ResponseHeaders, ac3ResponseHeaders,
emptyExtractorInput); emptyExtractorInput);
// The format indicates WebVTT so we expect a WebVTT extractor.
assertThat(result.extractor.getClass()).isEqualTo(WebvttExtractor.class);
}
@Test
public void createExtractor_onFailedSniff_fallsBackOnHttpContentType() throws Exception {
ExtractorInput emptyExtractorInput = new FakeExtractorInput.Builder().build();
BundledHlsMediaChunkExtractor result =
new DefaultHlsExtractorFactory()
.createExtractor(
URI_WITH_MP4_EXTENSION,
new Format.Builder().build(),
/* muxedCaptionFormats= */ null,
timestampAdjuster,
ac3ResponseHeaders,
emptyExtractorInput);
// No format info, so we expect an AC-3 Extractor, as per HTTP Content-Type header.
assertThat(result.extractor.getClass()).isEqualTo(Ac3Extractor.class);
}
@Test
public void createExtractor_onFailedSniff_fallsBackOnFileExtension() throws Exception {
ExtractorInput emptyExtractorInput = new FakeExtractorInput.Builder().build();
BundledHlsMediaChunkExtractor result =
new DefaultHlsExtractorFactory()
.createExtractor(
URI_WITH_MP4_EXTENSION,
new Format.Builder().build(),
/* muxedCaptionFormats= */ null,
timestampAdjuster,
/* responseHeaders= */ ImmutableMap.of(),
emptyExtractorInput);
// No format info, and no HTTP headers, so we expect an fMP4 extractor, as per file extension.
assertThat(result.extractor.getClass()).isEqualTo(FragmentedMp4Extractor.class);
}
@Test
public void createExtractor_onFailedSniff_fallsBackOnTsExtractor() throws Exception {
ExtractorInput emptyExtractorInput = new FakeExtractorInput.Builder().build();
BundledHlsMediaChunkExtractor result =
new DefaultHlsExtractorFactory()
.createExtractor(
Uri.parse("http://path/no_extension"),
new Format.Builder().build(),
/* muxedCaptionFormats= */ null,
timestampAdjuster,
/* responseHeaders= */ ImmutableMap.of(),
emptyExtractorInput);
// There's no information for inferring the file type, we expect the factory to fall back on
// Transport Stream.
assertThat(result.extractor.getClass()).isEqualTo(TsExtractor.class); assertThat(result.extractor.getClass()).isEqualTo(TsExtractor.class);
} }
} }