Skip to content

Commit

Permalink
Fallback to inferred file types when sniffing fails
Browse files Browse the repository at this point in the history
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
  • Loading branch information
AquilesCanta authored and marcbaechinger committed Apr 9, 2021
1 parent 3dae045 commit 21326e6
Show file tree
Hide file tree
Showing 2 changed files with 74 additions and 11 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -125,9 +125,13 @@ public BundledHlsMediaChunkExtractor createExtractor(
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;
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,12 +24,14 @@
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;
Expand All @@ -42,14 +44,15 @@
@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<>();
Expand All @@ -69,7 +72,7 @@ public void createExtractor_withFileTypeInFormat_returnsExtractorMatchingFormat(
BundledHlsMediaChunkExtractor result =
new DefaultHlsExtractorFactory()
.createExtractor(
tsUri,
URI_WITH_TS_EXTENSION,
webVttFormat,
/* muxedCaptionFormats= */ null,
timestampAdjuster,
Expand All @@ -93,7 +96,7 @@ public void createExtractor_withFileTypeInFormat_returnsExtractorMatchingFormat(
BundledHlsMediaChunkExtractor result =
new DefaultHlsExtractorFactory()
.createExtractor(
tsUri,
URI_WITH_TS_EXTENSION,
webVttFormat,
/* muxedCaptionFormats= */ null,
timestampAdjuster,
Expand All @@ -115,7 +118,7 @@ public void createExtractor_withFileTypeInUri_returnsExtractorMatchingUri() thro
BundledHlsMediaChunkExtractor result =
new DefaultHlsExtractorFactory()
.createExtractor(
tsUri,
URI_WITH_TS_EXTENSION,
webVttFormat,
/* muxedCaptionFormats= */ null,
timestampAdjuster,
Expand All @@ -138,7 +141,7 @@ public void createExtractor_withFileTypeNotInMediaInfo_returnsExpectedExtractor(
BundledHlsMediaChunkExtractor result =
new DefaultHlsExtractorFactory()
.createExtractor(
tsUri,
URI_WITH_TS_EXTENSION,
webVttFormat,
/* muxedCaptionFormats= */ null,
timestampAdjuster,
Expand All @@ -149,19 +152,75 @@ public void createExtractor_withFileTypeNotInMediaInfo_returnsExpectedExtractor(
}

@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);
}
}

0 comments on commit 21326e6

Please sign in to comment.