diff --git a/library/core/src/main/java/com/google/android/exoplayer2/extractor/DefaultExtractorsFactory.java b/library/core/src/main/java/com/google/android/exoplayer2/extractor/DefaultExtractorsFactory.java index ccc5c0eb3ef..87165e7a9b0 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/extractor/DefaultExtractorsFactory.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/extractor/DefaultExtractorsFactory.java @@ -67,6 +67,7 @@ public final class DefaultExtractorsFactory implements ExtractorsFactory { } private @MatroskaExtractor.Flags int matroskaFlags; + private @Mp4Extractor.Flags int mp4Flags; private @FragmentedMp4Extractor.Flags int fragmentedMp4Flags; private @Mp3Extractor.Flags int mp3Flags; private @TsExtractor.Mode int tsMode; @@ -89,6 +90,18 @@ public synchronized DefaultExtractorsFactory setMatroskaExtractorFlags( 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. * @@ -145,7 +158,7 @@ public synchronized Extractor[] createExtractors() { Extractor[] extractors = new Extractor[FLAC_EXTRACTOR_CONSTRUCTOR == null ? 11 : 12]; extractors[0] = new MatroskaExtractor(matroskaFlags); extractors[1] = new FragmentedMp4Extractor(fragmentedMp4Flags); - extractors[2] = new Mp4Extractor(); + extractors[2] = new Mp4Extractor(mp4Flags); extractors[3] = new Mp3Extractor(mp3Flags); extractors[4] = new AdtsExtractor(); extractors[5] = new Ac3Extractor(); diff --git a/library/core/src/main/java/com/google/android/exoplayer2/extractor/mp4/AtomParsers.java b/library/core/src/main/java/com/google/android/exoplayer2/extractor/mp4/AtomParsers.java index 86bdc2b41ca..1c4ca995f69 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/extractor/mp4/AtomParsers.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/extractor/mp4/AtomParsers.java @@ -60,11 +60,13 @@ * @param duration The duration in units of the timescale declared in the mvhd atom, or * {@link C#TIME_UNSET} if the duration should be parsed from the tkhd atom. * @param drmInitData {@link DrmInitData} to be included in the format. + * @param ignoreEditLists Whether to ignore any edit lists in the trak box. * @param isQuickTime True for QuickTime media. False otherwise. * @return A {@link Track} instance, or {@code null} if the track's type isn't supported. */ public static Track parseTrak(Atom.ContainerAtom trak, Atom.LeafAtom mvhd, long duration, - DrmInitData drmInitData, boolean isQuickTime) throws ParserException { + DrmInitData drmInitData, boolean ignoreEditLists, boolean isQuickTime) + throws ParserException { Atom.ContainerAtom mdia = trak.getContainerAtomOfType(Atom.TYPE_mdia); int trackType = parseHdlr(mdia.getLeafAtomOfType(Atom.TYPE_hdlr).data); if (trackType == C.TRACK_TYPE_UNKNOWN) { @@ -88,11 +90,17 @@ public static Track parseTrak(Atom.ContainerAtom trak, Atom.LeafAtom mvhd, long Pair mdhdData = parseMdhd(mdia.getLeafAtomOfType(Atom.TYPE_mdhd).data); StsdData stsdData = parseStsd(stbl.getLeafAtomOfType(Atom.TYPE_stsd).data, tkhdData.id, tkhdData.rotationDegrees, mdhdData.second, drmInitData, isQuickTime); - Pair edtsData = parseEdts(trak.getContainerAtomOfType(Atom.TYPE_edts)); + long[] editListDurations = null; + long[] editListMediaTimes = null; + if (!ignoreEditLists) { + Pair edtsData = parseEdts(trak.getContainerAtomOfType(Atom.TYPE_edts)); + editListDurations = edtsData.first; + editListMediaTimes = edtsData.second; + } return stsdData.format == null ? null : new Track(tkhdData.id, trackType, mdhdData.first, movieTimescale, durationUs, stsdData.format, stsdData.requiredSampleTransformation, stsdData.trackEncryptionBoxes, - stsdData.nalUnitLengthFieldLength, edtsData.first, edtsData.second); + stsdData.nalUnitLengthFieldLength, editListDurations, editListMediaTimes); } /** @@ -783,7 +791,7 @@ private static void parseVideoSampleEntry(ParsableByteArray parent, int atomType * * @param edtsAtom edts (edit box) atom to decode. * @return Pair of edit list durations and edit list media times, or a pair of nulls if they are - * not present. + * not present. */ private static Pair parseEdts(Atom.ContainerAtom edtsAtom) { Atom.LeafAtom elst; diff --git a/library/core/src/main/java/com/google/android/exoplayer2/extractor/mp4/FragmentedMp4Extractor.java b/library/core/src/main/java/com/google/android/exoplayer2/extractor/mp4/FragmentedMp4Extractor.java index 4807e052772..867e4501faf 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/extractor/mp4/FragmentedMp4Extractor.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/extractor/mp4/FragmentedMp4Extractor.java @@ -74,7 +74,7 @@ public Extractor[] createExtractors() { @Retention(RetentionPolicy.SOURCE) @IntDef(flag = true, value = {FLAG_WORKAROUND_EVERY_VIDEO_FRAME_IS_SYNC_FRAME, FLAG_WORKAROUND_IGNORE_TFDT_BOX, FLAG_ENABLE_EMSG_TRACK, FLAG_ENABLE_CEA608_TRACK, - FLAG_SIDELOADED}) + FLAG_SIDELOADED, FLAG_WORKAROUND_IGNORE_EDIT_LISTS}) public @interface Flags {} /** * Flag to work around an issue in some video streams where every frame is marked as a sync frame. @@ -103,6 +103,10 @@ public Extractor[] createExtractors() { * container. */ private static final int FLAG_SIDELOADED = 16; + /** + * Flag to ignore any edit lists in the stream. + */ + public static final int FLAG_WORKAROUND_IGNORE_EDIT_LISTS = 32; private static final String TAG = "FragmentedMp4Extractor"; private static final int SAMPLE_GROUP_TYPE_seig = Util.getIntegerCodeForString("seig"); @@ -432,7 +436,7 @@ private void onMoovContainerAtomRead(ContainerAtom moov) throws ParserException Atom.ContainerAtom atom = moov.containerChildren.get(i); if (atom.type == Atom.TYPE_trak) { Track track = AtomParsers.parseTrak(atom, moov.getLeafAtomOfType(Atom.TYPE_mvhd), duration, - drmInitData, false); + drmInitData, (flags & FLAG_WORKAROUND_IGNORE_EDIT_LISTS) != 0, false); if (track != null) { tracks.put(track.id, track); } diff --git a/library/core/src/main/java/com/google/android/exoplayer2/extractor/mp4/Mp4Extractor.java b/library/core/src/main/java/com/google/android/exoplayer2/extractor/mp4/Mp4Extractor.java index d3fe9e0d058..473e17e3e0f 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/extractor/mp4/Mp4Extractor.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/extractor/mp4/Mp4Extractor.java @@ -28,6 +28,7 @@ import com.google.android.exoplayer2.extractor.SeekMap; import com.google.android.exoplayer2.extractor.TrackOutput; import com.google.android.exoplayer2.extractor.mp4.Atom.ContainerAtom; +import com.google.android.exoplayer2.extractor.mp4.FragmentedMp4Extractor.Flags; import com.google.android.exoplayer2.metadata.Metadata; import com.google.android.exoplayer2.util.Assertions; import com.google.android.exoplayer2.util.NalUnitUtil; @@ -57,6 +58,17 @@ public Extractor[] createExtractors() { }; + /** + * Flags controlling the behavior of the extractor. + */ + @Retention(RetentionPolicy.SOURCE) + @IntDef(flag = true, value = {FLAG_WORKAROUND_IGNORE_EDIT_LISTS}) + public @interface Flags {} + /** + * Flag to ignore any edit lists in the stream. + */ + public static final int FLAG_WORKAROUND_IGNORE_EDIT_LISTS = 1; + /** * Parser states. */ @@ -76,6 +88,8 @@ public Extractor[] createExtractors() { */ private static final long RELOAD_MINIMUM_SEEK_DISTANCE = 256 * 1024; + private final @Flags int flags; + // Temporary arrays. private final ParsableByteArray nalStartCode; private final ParsableByteArray nalLength; @@ -98,7 +112,21 @@ public Extractor[] createExtractors() { private long durationUs; private boolean isQuickTime; + /** + * Creates a new extractor for unfragmented MP4 streams. + */ public Mp4Extractor() { + this(0); + } + + /** + * Creates a new extractor for unfragmented MP4 streams, using the specified flags to control the + * extractor's behavior. + * + * @param flags Flags that control the extractor's behavior. + */ + public Mp4Extractor(@Flags int flags) { + this.flags = flags; atomHeader = new ParsableByteArray(Atom.LONG_HEADER_SIZE); containerAtoms = new Stack<>(); nalStartCode = new ParsableByteArray(NalUnitUtil.NAL_START_CODE); @@ -345,7 +373,7 @@ private void processMoovAtom(ContainerAtom moov) throws ParserException { } Track track = AtomParsers.parseTrak(atom, moov.getLeafAtomOfType(Atom.TYPE_mvhd), - C.TIME_UNSET, null, isQuickTime); + C.TIME_UNSET, null, (flags & FLAG_WORKAROUND_IGNORE_EDIT_LISTS) != 0, isQuickTime); if (track == null) { continue; } diff --git a/library/core/src/main/java/com/google/android/exoplayer2/extractor/mp4/Track.java b/library/core/src/main/java/com/google/android/exoplayer2/extractor/mp4/Track.java index 7ac31587946..3adc5a8972d 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/extractor/mp4/Track.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/extractor/mp4/Track.java @@ -81,12 +81,12 @@ public final class Track { /** * Durations of edit list segments in the movie timescale. Null if there is no edit list. */ - public final long[] editListDurations; + @Nullable public final long[] editListDurations; /** * Media times for edit list segments in the track timescale. Null if there is no edit list. */ - public final long[] editListMediaTimes; + @Nullable public final long[] editListMediaTimes; /** * For H264 video tracks, the length in bytes of the NALUnitLength field in each sample. 0 for @@ -99,7 +99,7 @@ public final class Track { public Track(int id, int type, long timescale, long movieTimescale, long durationUs, Format format, @Transformation int sampleTransformation, @Nullable TrackEncryptionBox[] sampleDescriptionEncryptionBoxes, int nalUnitLengthFieldLength, - long[] editListDurations, long[] editListMediaTimes) { + @Nullable long[] editListDurations, @Nullable long[] editListMediaTimes) { this.id = id; this.type = type; this.timescale = timescale;