blob: a71796cbb8a5ef0572ddd2e6febd3e69c79f613c [file] [log] [blame]
/*
* Copyright (C) 2016 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.extractor;
import static com.google.android.exoplayer2.util.FileTypes.inferFileTypeFromResponseHeaders;
import static com.google.android.exoplayer2.util.FileTypes.inferFileTypeFromUri;
import android.net.Uri;
import androidx.annotation.GuardedBy;
import androidx.annotation.Nullable;
import com.google.android.exoplayer2.PlaybackException;
import com.google.android.exoplayer2.Player;
import com.google.android.exoplayer2.extractor.amr.AmrExtractor;
import com.google.android.exoplayer2.extractor.flac.FlacExtractor;
import com.google.android.exoplayer2.extractor.flv.FlvExtractor;
import com.google.android.exoplayer2.extractor.jpeg.JpegExtractor;
import com.google.android.exoplayer2.extractor.mkv.MatroskaExtractor;
import com.google.android.exoplayer2.extractor.mp3.Mp3Extractor;
import com.google.android.exoplayer2.extractor.mp4.FragmentedMp4Extractor;
import com.google.android.exoplayer2.extractor.mp4.Mp4Extractor;
import com.google.android.exoplayer2.extractor.ogg.OggExtractor;
import com.google.android.exoplayer2.extractor.ts.Ac3Extractor;
import com.google.android.exoplayer2.extractor.ts.Ac4Extractor;
import com.google.android.exoplayer2.extractor.ts.AdtsExtractor;
import com.google.android.exoplayer2.extractor.ts.DefaultTsPayloadReaderFactory;
import com.google.android.exoplayer2.extractor.ts.PsExtractor;
import com.google.android.exoplayer2.extractor.ts.TsExtractor;
import com.google.android.exoplayer2.extractor.ts.TsPayloadReader;
import com.google.android.exoplayer2.extractor.wav.WavExtractor;
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;
import java.util.concurrent.atomic.AtomicBoolean;
/**
* An {@link ExtractorsFactory} that provides an array of extractors for the following formats:
*
* <ul>
* <li>MP4, including M4A ({@link Mp4Extractor})
* <li>fMP4 ({@link FragmentedMp4Extractor})
* <li>Matroska and WebM ({@link MatroskaExtractor})
* <li>Ogg Vorbis/FLAC ({@link OggExtractor}
* <li>MP3 ({@link Mp3Extractor})
* <li>AAC ({@link AdtsExtractor})
* <li>MPEG TS ({@link TsExtractor})
* <li>MPEG PS ({@link PsExtractor})
* <li>FLV ({@link FlvExtractor})
* <li>WAV ({@link WavExtractor})
* <li>AC3 ({@link Ac3Extractor})
* <li>AC4 ({@link Ac4Extractor})
* <li>AMR ({@link AmrExtractor})
* <li>FLAC
* <ul>
* <li>If available, the FLAC extension's {@code
* com.google.android.exoplayer2.ext.flac.FlacExtractor} is used.
* <li>Otherwise, the core {@link FlacExtractor} is used. Note that Android devices do not
* generally include a FLAC decoder before API 27. This can be worked around by using
* the FLAC extension or the FFmpeg extension.
* </ul>
* <li>JPEG ({@link JpegExtractor})
* </ul>
*/
public final class DefaultExtractorsFactory implements ExtractorsFactory {
// Extractors order is optimized according to
// https://docs.google.com/document/d/1w2mKaWMxfz2Ei8-LdxqbPs1VLe_oudB-eryXXw9OvQQ.
// The JPEG extractor appears after audio/video extractors because we expect audio/video input to
// be more common.
private static final int[] DEFAULT_EXTRACTOR_ORDER =
new int[] {
FileTypes.FLV,
FileTypes.FLAC,
FileTypes.WAV,
FileTypes.MP4,
FileTypes.AMR,
FileTypes.PS,
FileTypes.OGG,
FileTypes.TS,
FileTypes.MATROSKA,
FileTypes.ADTS,
FileTypes.AC3,
FileTypes.AC4,
FileTypes.MP3,
FileTypes.JPEG,
};
private static final FlacExtensionLoader FLAC_EXTENSION_LOADER = new FlacExtensionLoader();
private boolean constantBitrateSeekingEnabled;
private boolean constantBitrateSeekingAlwaysEnabled;
@AdtsExtractor.Flags private int adtsFlags;
@AmrExtractor.Flags private int amrFlags;
@FlacExtractor.Flags private int flacFlags;
@MatroskaExtractor.Flags private int matroskaFlags;
@Mp4Extractor.Flags private int mp4Flags;
@FragmentedMp4Extractor.Flags private int fragmentedMp4Flags;
@Mp3Extractor.Flags private int mp3Flags;
@TsExtractor.Mode private int tsMode;
@DefaultTsPayloadReaderFactory.Flags private int tsFlags;
private int tsTimestampSearchBytes;
public DefaultExtractorsFactory() {
tsMode = TsExtractor.MODE_SINGLE_PMT;
tsTimestampSearchBytes = TsExtractor.DEFAULT_TIMESTAMP_SEARCH_BYTES;
}
/**
* Convenience method to set whether approximate seeking using constant bitrate assumptions should
* be enabled for all extractors that support it. If set to true, the flags required to enable
* this functionality will be OR'd with those passed to the setters when creating extractor
* instances. If set to false then the flags passed to the setters will be used without
* modification.
*
* @param constantBitrateSeekingEnabled Whether approximate seeking using a constant bitrate
* assumption should be enabled for all extractors that support it.
* @return The factory, for convenience.
*/
public synchronized DefaultExtractorsFactory setConstantBitrateSeekingEnabled(
boolean constantBitrateSeekingEnabled) {
this.constantBitrateSeekingEnabled = constantBitrateSeekingEnabled;
return this;
}
/**
* Convenience method to set whether approximate seeking using constant bitrate assumptions should
* be enabled for all extractors that support it, and if it should be enabled even if the content
* length (and hence the duration of the media) is unknown. If set to true, the flags required to
* enable this functionality will be OR'd with those passed to the setters when creating extractor
* instances. If set to false then the flags passed to the setters will be used without
* modification.
*
* <p>When seeking into content where the length is unknown, application code should ensure that
* requested seek positions are valid, or should be ready to handle playback failures reported
* through {@link Player.Listener#onPlayerError} with {@link PlaybackException#errorCode} set to
* {@link PlaybackException#ERROR_CODE_IO_READ_POSITION_OUT_OF_RANGE}.
*
* @param constantBitrateSeekingAlwaysEnabled Whether approximate seeking using a constant bitrate
* assumption should be enabled for all extractors that support it, including when the content
* duration is unknown.
* @return The factory, for convenience.
*/
public synchronized DefaultExtractorsFactory setConstantBitrateSeekingAlwaysEnabled(
boolean constantBitrateSeekingAlwaysEnabled) {
this.constantBitrateSeekingAlwaysEnabled = constantBitrateSeekingAlwaysEnabled;
return this;
}
/**
* Sets flags for {@link AdtsExtractor} instances created by the factory.
*
* @see AdtsExtractor#AdtsExtractor(int)
* @param flags The flags to use.
* @return The factory, for convenience.
*/
public synchronized DefaultExtractorsFactory setAdtsExtractorFlags(
@AdtsExtractor.Flags int flags) {
this.adtsFlags = flags;
return this;
}
/**
* Sets flags for {@link AmrExtractor} instances created by the factory.
*
* @see AmrExtractor#AmrExtractor(int)
* @param flags The flags to use.
* @return The factory, for convenience.
*/
public synchronized DefaultExtractorsFactory setAmrExtractorFlags(@AmrExtractor.Flags int flags) {
this.amrFlags = flags;
return this;
}
/**
* Sets flags for {@link FlacExtractor} instances created by the factory. The flags are also used
* by {@code com.google.android.exoplayer2.ext.flac.FlacExtractor} instances if the FLAC extension
* is being used.
*
* @see FlacExtractor#FlacExtractor(int)
* @param flags The flags to use.
* @return The factory, for convenience.
*/
public synchronized DefaultExtractorsFactory setFlacExtractorFlags(
@FlacExtractor.Flags int flags) {
this.flacFlags = flags;
return this;
}
/**
* Sets flags for {@link MatroskaExtractor} instances created by the factory.
*
* @see MatroskaExtractor#MatroskaExtractor(int)
* @param flags The flags to use.
* @return The factory, for convenience.
*/
public synchronized DefaultExtractorsFactory setMatroskaExtractorFlags(
@MatroskaExtractor.Flags int flags) {
this.matroskaFlags = flags;
return this;
}
/**
* Sets flags for {@link Mp4Extractor} instances created by the factory.
*
* @see Mp4Extractor#Mp4Extractor(int)
* @param flags The flags to use.
* @return The factory, for convenience.
*/
public synchronized DefaultExtractorsFactory setMp4ExtractorFlags(@Mp4Extractor.Flags int flags) {
this.mp4Flags = flags;
return this;
}
/**
* Sets flags for {@link FragmentedMp4Extractor} instances created by the factory.
*
* @see FragmentedMp4Extractor#FragmentedMp4Extractor(int)
* @param flags The flags to use.
* @return The factory, for convenience.
*/
public synchronized DefaultExtractorsFactory setFragmentedMp4ExtractorFlags(
@FragmentedMp4Extractor.Flags int flags) {
this.fragmentedMp4Flags = flags;
return this;
}
/**
* Sets flags for {@link Mp3Extractor} instances created by the factory.
*
* @see Mp3Extractor#Mp3Extractor(int)
* @param flags The flags to use.
* @return The factory, for convenience.
*/
public synchronized DefaultExtractorsFactory setMp3ExtractorFlags(@Mp3Extractor.Flags int flags) {
mp3Flags = flags;
return this;
}
/**
* Sets the mode for {@link TsExtractor} instances created by the factory.
*
* @see TsExtractor#TsExtractor(int, TimestampAdjuster, TsPayloadReader.Factory, int)
* @param mode The mode to use.
* @return The factory, for convenience.
*/
public synchronized DefaultExtractorsFactory setTsExtractorMode(@TsExtractor.Mode int mode) {
tsMode = mode;
return this;
}
/**
* Sets flags for {@link DefaultTsPayloadReaderFactory}s used by {@link TsExtractor} instances
* created by the factory.
*
* @see TsExtractor#TsExtractor(int)
* @param flags The flags to use.
* @return The factory, for convenience.
*/
public synchronized DefaultExtractorsFactory setTsExtractorFlags(
@DefaultTsPayloadReaderFactory.Flags int flags) {
tsFlags = flags;
return this;
}
/**
* Sets the number of bytes searched to find a timestamp for {@link TsExtractor} instances created
* by the factory.
*
* @see TsExtractor#TsExtractor(int, TimestampAdjuster, TsPayloadReader.Factory, int)
* @param timestampSearchBytes The number of search bytes to use.
* @return The factory, for convenience.
*/
public synchronized DefaultExtractorsFactory setTsExtractorTimestampSearchBytes(
int timestampSearchBytes) {
tsTimestampSearchBytes = timestampSearchBytes;
return this;
}
@Override
public synchronized Extractor[] createExtractors() {
return createExtractors(Uri.EMPTY, new HashMap<>());
}
@Override
public synchronized Extractor[] createExtractors(
Uri uri, Map<String, List<String>> responseHeaders) {
List<Extractor> extractors = new ArrayList<>(/* initialCapacity= */ 14);
@FileTypes.Type
int responseHeadersInferredFileType = inferFileTypeFromResponseHeaders(responseHeaders);
if (responseHeadersInferredFileType != FileTypes.UNKNOWN) {
addExtractorsForFileType(responseHeadersInferredFileType, extractors);
}
@FileTypes.Type int uriInferredFileType = inferFileTypeFromUri(uri);
if (uriInferredFileType != FileTypes.UNKNOWN
&& uriInferredFileType != responseHeadersInferredFileType) {
addExtractorsForFileType(uriInferredFileType, extractors);
}
for (int fileType : DEFAULT_EXTRACTOR_ORDER) {
if (fileType != responseHeadersInferredFileType && fileType != uriInferredFileType) {
addExtractorsForFileType(fileType, extractors);
}
}
return extractors.toArray(new Extractor[extractors.size()]);
}
private void addExtractorsForFileType(@FileTypes.Type int fileType, List<Extractor> extractors) {
switch (fileType) {
case FileTypes.AC3:
extractors.add(new Ac3Extractor());
break;
case FileTypes.AC4:
extractors.add(new Ac4Extractor());
break;
case FileTypes.ADTS:
extractors.add(
new AdtsExtractor(
adtsFlags
| (constantBitrateSeekingEnabled
? AdtsExtractor.FLAG_ENABLE_CONSTANT_BITRATE_SEEKING
: 0)
| (constantBitrateSeekingAlwaysEnabled
? AdtsExtractor.FLAG_ENABLE_CONSTANT_BITRATE_SEEKING_ALWAYS
: 0)));
break;
case FileTypes.AMR:
extractors.add(
new AmrExtractor(
amrFlags
| (constantBitrateSeekingEnabled
? AmrExtractor.FLAG_ENABLE_CONSTANT_BITRATE_SEEKING
: 0)
| (constantBitrateSeekingAlwaysEnabled
? AmrExtractor.FLAG_ENABLE_CONSTANT_BITRATE_SEEKING_ALWAYS
: 0)));
break;
case FileTypes.FLAC:
@Nullable Extractor flacExtractor = FLAC_EXTENSION_LOADER.getExtractor(flacFlags);
if (flacExtractor != null) {
extractors.add(flacExtractor);
} else {
extractors.add(new FlacExtractor(flacFlags));
}
break;
case FileTypes.FLV:
extractors.add(new FlvExtractor());
break;
case FileTypes.MATROSKA:
extractors.add(new MatroskaExtractor(matroskaFlags));
break;
case FileTypes.MP3:
extractors.add(
new Mp3Extractor(
mp3Flags
| (constantBitrateSeekingEnabled
? Mp3Extractor.FLAG_ENABLE_CONSTANT_BITRATE_SEEKING
: 0)
| (constantBitrateSeekingAlwaysEnabled
? Mp3Extractor.FLAG_ENABLE_CONSTANT_BITRATE_SEEKING_ALWAYS
: 0)));
break;
case FileTypes.MP4:
extractors.add(new FragmentedMp4Extractor(fragmentedMp4Flags));
extractors.add(new Mp4Extractor(mp4Flags));
break;
case FileTypes.OGG:
extractors.add(new OggExtractor());
break;
case FileTypes.PS:
extractors.add(new PsExtractor());
break;
case FileTypes.TS:
extractors.add(new TsExtractor(tsMode, tsFlags, tsTimestampSearchBytes));
break;
case FileTypes.WAV:
extractors.add(new WavExtractor());
break;
case FileTypes.JPEG:
extractors.add(new JpegExtractor());
break;
case FileTypes.WEBVTT:
case FileTypes.UNKNOWN:
default:
break;
}
}
private static final class FlacExtensionLoader {
private final AtomicBoolean extensionLoaded;
@GuardedBy("extensionLoaded")
@Nullable
private Constructor<? extends Extractor> extractorConstructor;
public FlacExtensionLoader() {
extensionLoaded = new AtomicBoolean(false);
}
@Nullable
public Extractor getExtractor(int flags) {
@Nullable
Constructor<? extends Extractor> extractorConstructor = maybeLoadExtractorConstructor();
if (extractorConstructor == null) {
return null;
}
try {
return extractorConstructor.newInstance(flags);
} catch (Exception e) {
throw new IllegalStateException("Unexpected error creating FLAC extractor", e);
}
}
@Nullable
private Constructor<? extends Extractor> maybeLoadExtractorConstructor() {
synchronized (extensionLoaded) {
if (extensionLoaded.get()) {
return extractorConstructor;
}
try {
@SuppressWarnings("nullness:argument")
boolean isFlacNativeLibraryAvailable =
Boolean.TRUE.equals(
Class.forName("com.google.android.exoplayer2.ext.flac.FlacLibrary")
.getMethod("isAvailable")
.invoke(/* obj= */ null));
if (isFlacNativeLibraryAvailable) {
extractorConstructor =
Class.forName("com.google.android.exoplayer2.ext.flac.FlacExtractor")
.asSubclass(Extractor.class)
.getConstructor(int.class);
}
} catch (ClassNotFoundException e) {
// Expected if the app was built without the FLAC extension.
} catch (Exception e) {
// The FLAC extension is present, but instantiation failed.
throw new RuntimeException("Error instantiating FLAC extension", e);
}
extensionLoaded.set(true);
return extractorConstructor;
}
}
}
}