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)) {
return new BundledHlsMediaChunkExtractor(extractor, format, timestampAdjuster);
}
if (fileType == FileTypes.TS) {
// Fall back on TsExtractor to handle TS streams with an EXT-X-MAP tag. See
// https://github.com/google/ExoPlayer/issues/8219.
if (fallBackExtractor == null
&& (fileType == formatInferredFileType
|| 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;
}
}

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.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 com.google.common.collect.ImmutableMap;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
@ -42,14 +44,15 @@ import org.junit.runner.RunWith;
@RunWith(AndroidJUnit4.class)
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 TimestampAdjuster timestampAdjuster;
private Map<String, List<String>> ac3ResponseHeaders;
@Before
public void setUp() {
tsUri = Uri.parse("http://path/filename.ts");
webVttFormat = new Format.Builder().setSampleMimeType(MimeTypes.TEXT_VTT).build();
timestampAdjuster = new TimestampAdjuster(/* firstSampleTimestampUs= */ 0);
ac3ResponseHeaders = new HashMap<>();
@ -69,7 +72,7 @@ public class DefaultHlsExtractorFactoryTest {
BundledHlsMediaChunkExtractor result =
new DefaultHlsExtractorFactory()
.createExtractor(
tsUri,
URI_WITH_TS_EXTENSION,
webVttFormat,
/* muxedCaptionFormats= */ null,
timestampAdjuster,
@ -93,7 +96,7 @@ public class DefaultHlsExtractorFactoryTest {
BundledHlsMediaChunkExtractor result =
new DefaultHlsExtractorFactory()
.createExtractor(
tsUri,
URI_WITH_TS_EXTENSION,
webVttFormat,
/* muxedCaptionFormats= */ null,
timestampAdjuster,
@ -115,7 +118,7 @@ public class DefaultHlsExtractorFactoryTest {
BundledHlsMediaChunkExtractor result =
new DefaultHlsExtractorFactory()
.createExtractor(
tsUri,
URI_WITH_TS_EXTENSION,
webVttFormat,
/* muxedCaptionFormats= */ null,
timestampAdjuster,
@ -138,7 +141,7 @@ public class DefaultHlsExtractorFactoryTest {
BundledHlsMediaChunkExtractor result =
new DefaultHlsExtractorFactory()
.createExtractor(
tsUri,
URI_WITH_TS_EXTENSION,
webVttFormat,
/* muxedCaptionFormats= */ null,
timestampAdjuster,
@ -149,19 +152,75 @@ public class DefaultHlsExtractorFactoryTest {
}
@Test
public void createExtractor_withNoMatchingExtractor_fallsBackOnTsExtractor() throws Exception {
public void createExtractor_onFailedSniff_fallsBackOnFormatInferred() throws Exception {
ExtractorInput emptyExtractorInput = new FakeExtractorInput.Builder().build();
BundledHlsMediaChunkExtractor result =
new DefaultHlsExtractorFactory()
.createExtractor(
tsUri,
URI_WITH_MP4_EXTENSION,
webVttFormat,
/* muxedCaptionFormats= */ null,
timestampAdjuster,
ac3ResponseHeaders,
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);
}
}