Commit ab6f9aea by ojw28 Committed by GitHub

Merge pull request #3381 from google/dev-v2-r2.5.4

r2.5.4
parents 1fdc11f2 04862bcc
Showing with 290 additions and 114 deletions
# Release notes # # Release notes #
### r2.5.4 ###
* Remove unnecessary media playlist fetches during playback of live HLS streams.
* Add the ability to inject a HLS playlist parser through `HlsMediaSource`.
* Fix potential `IndexOutOfBoundsException` when using `ImaMediaSource`
([#3334](https://github.com/google/ExoPlayer/issues/3334)).
* Fix an issue parsing MP4 content containing non-CENC sinf boxes.
* Fix memory leak when seeking with repeated periods.
* Fix playback position when `ExoPlayer.prepare` is called with `resetPosition`
set to false.
* Ignore MP4 edit lists that seem invalid
([#3351](https://github.com/google/ExoPlayer/issues/3351)).
* Add extractor flag for ignoring all MP4 edit lists
([#3358](https://github.com/google/ExoPlayer/issues/3358)).
* Improve extensibility by exposing public constructors for
`FrameworkMediaCrypto` and by making `DefaultDashChunkSource.getNextChunk`
non-final.
### r2.5.3 ### ### r2.5.3 ###
* IMA extension: Support skipping of skippable ads on AndroidTV and other * IMA extension: Support skipping of skippable ads on AndroidTV and other
......
...@@ -24,7 +24,7 @@ project.ext { ...@@ -24,7 +24,7 @@ project.ext {
supportLibraryVersion = '25.4.0' supportLibraryVersion = '25.4.0'
dexmakerVersion = '1.2' dexmakerVersion = '1.2'
mockitoVersion = '1.9.5' mockitoVersion = '1.9.5'
releaseVersion = 'r2.5.3' releaseVersion = 'r2.5.4'
modulePrefix = ':' modulePrefix = ':'
if (gradle.ext.has('exoplayerModulePrefix')) { if (gradle.ext.has('exoplayerModulePrefix')) {
modulePrefix += gradle.ext.exoplayerModulePrefix modulePrefix += gradle.ext.exoplayerModulePrefix
......
...@@ -16,8 +16,8 @@ ...@@ -16,8 +16,8 @@
<manifest xmlns:android="http://schemas.android.com/apk/res/android" <manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.google.android.exoplayer2.demo" package="com.google.android.exoplayer2.demo"
android:versionCode="2503" android:versionCode="2504"
android:versionName="2.5.3"> android:versionName="2.5.4">
<uses-permission android:name="android.permission.INTERNET"/> <uses-permission android:name="android.permission.INTERNET"/>
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"/> <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"/>
......
...@@ -344,11 +344,11 @@ ...@@ -344,11 +344,11 @@
"samples": [ "samples": [
{ {
"name": "Apple 4x3 basic stream", "name": "Apple 4x3 basic stream",
"uri": "https://devimages.apple.com.edgekey.net/streaming/examples/bipbop_4x3/bipbop_4x3_variant.m3u8" "uri": "https://devstreaming-cdn.apple.com/videos/streaming/examples/bipbop_4x3/bipbop_4x3_variant.m3u8"
}, },
{ {
"name": "Apple 16x9 basic stream", "name": "Apple 16x9 basic stream",
"uri": "https://devimages.apple.com.edgekey.net/streaming/examples/bipbop_16x9/bipbop_16x9_variant.m3u8" "uri": "https://devstreaming-cdn.apple.com/videos/streaming/examples/bipbop_16x9/bipbop_16x9_variant.m3u8"
}, },
{ {
"name": "Apple master playlist advanced (TS)", "name": "Apple master playlist advanced (TS)",
...@@ -360,11 +360,11 @@ ...@@ -360,11 +360,11 @@
}, },
{ {
"name": "Apple TS media playlist", "name": "Apple TS media playlist",
"uri": "https://devimages.apple.com.edgekey.net/streaming/examples/bipbop_4x3/gear1/prog_index.m3u8" "uri": "https://devstreaming-cdn.apple.com/videos/streaming/examples/bipbop_4x3/gear1/prog_index.m3u8"
}, },
{ {
"name": "Apple AAC media playlist", "name": "Apple AAC media playlist",
"uri": "https://devimages.apple.com.edgekey.net/streaming/examples/bipbop_4x3/gear0/prog_index.m3u8" "uri": "https://devstreaming-cdn.apple.com/videos/streaming/examples/bipbop_4x3/gear0/prog_index.m3u8"
}, },
{ {
"name": "Apple ID3 metadata", "name": "Apple ID3 metadata",
...@@ -381,11 +381,11 @@ ...@@ -381,11 +381,11 @@
}, },
{ {
"name": "Apple AAC 10s", "name": "Apple AAC 10s",
"uri": "https://devimages.apple.com.edgekey.net/streaming/examples/bipbop_4x3/gear0/fileSequence0.aac" "uri": "https://devstreaming-cdn.apple.com/videos/streaming/examples/bipbop_4x3/gear0/fileSequence0.aac"
}, },
{ {
"name": "Apple TS 10s", "name": "Apple TS 10s",
"uri": "https://devimages.apple.com.edgekey.net/streaming/examples/bipbop_4x3/gear1/fileSequence0.ts" "uri": "https://devstreaming-cdn.apple.com/videos/streaming/examples/bipbop_4x3/gear1/fileSequence0.ts"
}, },
{ {
"name": "Android screens (Matroska)", "name": "Android screens (Matroska)",
......
...@@ -34,18 +34,18 @@ public final class AtomParsersTest extends TestCase { ...@@ -34,18 +34,18 @@ public final class AtomParsersTest extends TestCase {
+ SAMPLE_COUNT + "0001000200030004"); + SAMPLE_COUNT + "0001000200030004");
public void testStz2Parsing4BitFieldSize() { public void testStz2Parsing4BitFieldSize() {
verifyParsing(new Atom.LeafAtom(Atom.TYPE_stsz, new ParsableByteArray(FOUR_BIT_STZ2))); verifyStz2Parsing(new Atom.LeafAtom(Atom.TYPE_stsz, new ParsableByteArray(FOUR_BIT_STZ2)));
} }
public void testStz2Parsing8BitFieldSize() { public void testStz2Parsing8BitFieldSize() {
verifyParsing(new Atom.LeafAtom(Atom.TYPE_stsz, new ParsableByteArray(EIGHT_BIT_STZ2))); verifyStz2Parsing(new Atom.LeafAtom(Atom.TYPE_stsz, new ParsableByteArray(EIGHT_BIT_STZ2)));
} }
public void testStz2Parsing16BitFieldSize() { public void testStz2Parsing16BitFieldSize() {
verifyParsing(new Atom.LeafAtom(Atom.TYPE_stsz, new ParsableByteArray(SIXTEEN_BIT_STZ2))); verifyStz2Parsing(new Atom.LeafAtom(Atom.TYPE_stsz, new ParsableByteArray(SIXTEEN_BIT_STZ2)));
} }
private void verifyParsing(Atom.LeafAtom stz2Atom) { private static void verifyStz2Parsing(Atom.LeafAtom stz2Atom) {
AtomParsers.Stz2SampleSizeBox box = new AtomParsers.Stz2SampleSizeBox(stz2Atom); AtomParsers.Stz2SampleSizeBox box = new AtomParsers.Stz2SampleSizeBox(stz2Atom);
assertEquals(4, box.getSampleCount()); assertEquals(4, box.getSampleCount());
assertFalse(box.isFixedSampleSize()); assertFalse(box.isFixedSampleSize());
......
...@@ -356,17 +356,17 @@ import java.util.concurrent.CopyOnWriteArraySet; ...@@ -356,17 +356,17 @@ import java.util.concurrent.CopyOnWriteArraySet;
@Override @Override
public boolean isPlayingAd() { public boolean isPlayingAd() {
return pendingSeekAcks == 0 && playbackInfo.periodId.isAd(); return !timeline.isEmpty() && pendingSeekAcks == 0 && playbackInfo.periodId.isAd();
} }
@Override @Override
public int getCurrentAdGroupIndex() { public int getCurrentAdGroupIndex() {
return pendingSeekAcks == 0 ? playbackInfo.periodId.adGroupIndex : C.INDEX_UNSET; return isPlayingAd() ? playbackInfo.periodId.adGroupIndex : C.INDEX_UNSET;
} }
@Override @Override
public int getCurrentAdIndexInAdGroup() { public int getCurrentAdIndexInAdGroup() {
return pendingSeekAcks == 0 ? playbackInfo.periodId.adIndexInAdGroup : C.INDEX_UNSET; return isPlayingAd() ? playbackInfo.periodId.adIndexInAdGroup : C.INDEX_UNSET;
} }
@Override @Override
......
...@@ -430,6 +430,10 @@ import java.io.IOException; ...@@ -430,6 +430,10 @@ import java.io.IOException;
loadControl.onPrepared(); loadControl.onPrepared();
if (resetPosition) { if (resetPosition) {
playbackInfo = new PlaybackInfo(0, C.TIME_UNSET); playbackInfo = new PlaybackInfo(0, C.TIME_UNSET);
} else {
// The new start position is the current playback position.
playbackInfo = new PlaybackInfo(playbackInfo.periodId, playbackInfo.positionUs,
playbackInfo.contentPositionUs);
} }
this.mediaSource = mediaSource; this.mediaSource = mediaSource;
mediaSource.prepareSource(player, true, this); mediaSource.prepareSource(player, true, this);
...@@ -719,7 +723,8 @@ import java.io.IOException; ...@@ -719,7 +723,8 @@ import java.io.IOException;
// Clear the timeline, but keep the requested period if it is already prepared. // Clear the timeline, but keep the requested period if it is already prepared.
MediaPeriodHolder periodHolder = playingPeriodHolder; MediaPeriodHolder periodHolder = playingPeriodHolder;
while (periodHolder != null) { while (periodHolder != null) {
if (shouldKeepPeriodHolder(periodId, periodPositionUs, periodHolder)) { if (newPlayingPeriodHolder == null
&& shouldKeepPeriodHolder(periodId, periodPositionUs, periodHolder)) {
newPlayingPeriodHolder = periodHolder; newPlayingPeriodHolder = periodHolder;
} else { } else {
periodHolder.release(); periodHolder.release();
......
...@@ -31,13 +31,13 @@ public final class ExoPlayerLibraryInfo { ...@@ -31,13 +31,13 @@ public final class ExoPlayerLibraryInfo {
* The version of the library expressed as a string, for example "1.2.3". * The version of the library expressed as a string, for example "1.2.3".
*/ */
// Intentionally hardcoded. Do not derive from other constants (e.g. VERSION_INT) or vice versa. // Intentionally hardcoded. Do not derive from other constants (e.g. VERSION_INT) or vice versa.
public static final String VERSION = "2.5.3"; public static final String VERSION = "2.5.4";
/** /**
* The version of the library expressed as {@code "ExoPlayerLib/" + VERSION}. * The version of the library expressed as {@code "ExoPlayerLib/" + VERSION}.
*/ */
// Intentionally hardcoded. Do not derive from other constants (e.g. VERSION) or vice versa. // Intentionally hardcoded. Do not derive from other constants (e.g. VERSION) or vice versa.
public static final String VERSION_SLASHY = "ExoPlayerLib/2.5.3"; public static final String VERSION_SLASHY = "ExoPlayerLib/2.5.4";
/** /**
* The version of the library expressed as an integer, for example 1002003. * The version of the library expressed as an integer, for example 1002003.
...@@ -47,7 +47,7 @@ public final class ExoPlayerLibraryInfo { ...@@ -47,7 +47,7 @@ public final class ExoPlayerLibraryInfo {
* integer version 123045006 (123-045-006). * integer version 123045006 (123-045-006).
*/ */
// Intentionally hardcoded. Do not derive from other constants (e.g. VERSION) or vice versa. // Intentionally hardcoded. Do not derive from other constants (e.g. VERSION) or vice versa.
public static final int VERSION_INT = 2005003; public static final int VERSION_INT = 2005004;
/** /**
* Whether the library was compiled with {@link com.google.android.exoplayer2.util.Assertions} * Whether the library was compiled with {@link com.google.android.exoplayer2.util.Assertions}
......
...@@ -28,12 +28,29 @@ public final class FrameworkMediaCrypto implements ExoMediaCrypto { ...@@ -28,12 +28,29 @@ public final class FrameworkMediaCrypto implements ExoMediaCrypto {
private final MediaCrypto mediaCrypto; private final MediaCrypto mediaCrypto;
private final boolean forceAllowInsecureDecoderComponents; private final boolean forceAllowInsecureDecoderComponents;
/* package */ FrameworkMediaCrypto(MediaCrypto mediaCrypto, /**
* @param mediaCrypto The {@link MediaCrypto} to wrap.
*/
public FrameworkMediaCrypto(MediaCrypto mediaCrypto) {
this(mediaCrypto, false);
}
/**
* @param mediaCrypto The {@link MediaCrypto} to wrap.
* @param forceAllowInsecureDecoderComponents Whether to force
* {@link #requiresSecureDecoderComponent(String)} to return {@code false}, rather than
* {@link MediaCrypto#requiresSecureDecoderComponent(String)} of the wrapped
* {@link MediaCrypto}.
*/
public FrameworkMediaCrypto(MediaCrypto mediaCrypto,
boolean forceAllowInsecureDecoderComponents) { boolean forceAllowInsecureDecoderComponents) {
this.mediaCrypto = Assertions.checkNotNull(mediaCrypto); this.mediaCrypto = Assertions.checkNotNull(mediaCrypto);
this.forceAllowInsecureDecoderComponents = forceAllowInsecureDecoderComponents; this.forceAllowInsecureDecoderComponents = forceAllowInsecureDecoderComponents;
} }
/**
* Returns the wrapped {@link MediaCrypto}.
*/
public MediaCrypto getWrappedMediaCrypto() { public MediaCrypto getWrappedMediaCrypto() {
return mediaCrypto; return mediaCrypto;
} }
......
...@@ -67,6 +67,7 @@ public final class DefaultExtractorsFactory implements ExtractorsFactory { ...@@ -67,6 +67,7 @@ public final class DefaultExtractorsFactory implements ExtractorsFactory {
} }
private @MatroskaExtractor.Flags int matroskaFlags; private @MatroskaExtractor.Flags int matroskaFlags;
private @Mp4Extractor.Flags int mp4Flags;
private @FragmentedMp4Extractor.Flags int fragmentedMp4Flags; private @FragmentedMp4Extractor.Flags int fragmentedMp4Flags;
private @Mp3Extractor.Flags int mp3Flags; private @Mp3Extractor.Flags int mp3Flags;
private @TsExtractor.Mode int tsMode; private @TsExtractor.Mode int tsMode;
...@@ -90,6 +91,18 @@ public final class DefaultExtractorsFactory implements ExtractorsFactory { ...@@ -90,6 +91,18 @@ public final class DefaultExtractorsFactory implements ExtractorsFactory {
} }
/** /**
* 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. * Sets flags for {@link FragmentedMp4Extractor} instances created by the factory.
* *
* @see FragmentedMp4Extractor#FragmentedMp4Extractor(int) * @see FragmentedMp4Extractor#FragmentedMp4Extractor(int)
...@@ -145,7 +158,7 @@ public final class DefaultExtractorsFactory implements ExtractorsFactory { ...@@ -145,7 +158,7 @@ public final class DefaultExtractorsFactory implements ExtractorsFactory {
Extractor[] extractors = new Extractor[FLAC_EXTRACTOR_CONSTRUCTOR == null ? 11 : 12]; Extractor[] extractors = new Extractor[FLAC_EXTRACTOR_CONSTRUCTOR == null ? 11 : 12];
extractors[0] = new MatroskaExtractor(matroskaFlags); extractors[0] = new MatroskaExtractor(matroskaFlags);
extractors[1] = new FragmentedMp4Extractor(fragmentedMp4Flags); extractors[1] = new FragmentedMp4Extractor(fragmentedMp4Flags);
extractors[2] = new Mp4Extractor(); extractors[2] = new Mp4Extractor(mp4Flags);
extractors[3] = new Mp3Extractor(mp3Flags); extractors[3] = new Mp3Extractor(mp3Flags);
extractors[4] = new AdtsExtractor(); extractors[4] = new AdtsExtractor();
extractors[5] = new Ac3Extractor(); extractors[5] = new Ac3Extractor();
......
...@@ -60,11 +60,13 @@ import java.util.List; ...@@ -60,11 +60,13 @@ import java.util.List;
* @param duration The duration in units of the timescale declared in the mvhd atom, or * @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. * {@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 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. * @param isQuickTime True for QuickTime media. False otherwise.
* @return A {@link Track} instance, or {@code null} if the track's type isn't supported. * @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, 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); Atom.ContainerAtom mdia = trak.getContainerAtomOfType(Atom.TYPE_mdia);
int trackType = parseHdlr(mdia.getLeafAtomOfType(Atom.TYPE_hdlr).data); int trackType = parseHdlr(mdia.getLeafAtomOfType(Atom.TYPE_hdlr).data);
if (trackType == C.TRACK_TYPE_UNKNOWN) { if (trackType == C.TRACK_TYPE_UNKNOWN) {
...@@ -88,11 +90,17 @@ import java.util.List; ...@@ -88,11 +90,17 @@ import java.util.List;
Pair<Long, String> mdhdData = parseMdhd(mdia.getLeafAtomOfType(Atom.TYPE_mdhd).data); Pair<Long, String> mdhdData = parseMdhd(mdia.getLeafAtomOfType(Atom.TYPE_mdhd).data);
StsdData stsdData = parseStsd(stbl.getLeafAtomOfType(Atom.TYPE_stsd).data, tkhdData.id, StsdData stsdData = parseStsd(stbl.getLeafAtomOfType(Atom.TYPE_stsd).data, tkhdData.id,
tkhdData.rotationDegrees, mdhdData.second, drmInitData, isQuickTime); tkhdData.rotationDegrees, mdhdData.second, drmInitData, isQuickTime);
Pair<long[], long[]> edtsData = parseEdts(trak.getContainerAtomOfType(Atom.TYPE_edts)); long[] editListDurations = null;
long[] editListMediaTimes = null;
if (!ignoreEditLists) {
Pair<long[], long[]> edtsData = parseEdts(trak.getContainerAtomOfType(Atom.TYPE_edts));
editListDurations = edtsData.first;
editListMediaTimes = edtsData.second;
}
return stsdData.format == null ? null return stsdData.format == null ? null
: new Track(tkhdData.id, trackType, mdhdData.first, movieTimescale, durationUs, : new Track(tkhdData.id, trackType, mdhdData.first, movieTimescale, durationUs,
stsdData.format, stsdData.requiredSampleTransformation, stsdData.trackEncryptionBoxes, stsdData.format, stsdData.requiredSampleTransformation, stsdData.trackEncryptionBoxes,
stsdData.nalUnitLengthFieldLength, edtsData.first, edtsData.second); stsdData.nalUnitLengthFieldLength, editListDurations, editListMediaTimes);
} }
/** /**
...@@ -395,7 +403,11 @@ import java.util.List; ...@@ -395,7 +403,11 @@ import java.util.List;
hasSyncSample |= (editedFlags[i] & C.BUFFER_FLAG_KEY_FRAME) != 0; hasSyncSample |= (editedFlags[i] & C.BUFFER_FLAG_KEY_FRAME) != 0;
} }
if (!hasSyncSample) { if (!hasSyncSample) {
throw new ParserException("The edited sample sequence does not contain a sync sample."); // We don't support edit lists where the edited sample sequence doesn't contain a sync sample.
// Such edit lists are often (although not always) broken, so we ignore it and continue.
Log.w(TAG, "Ignoring edit list: Edited sample sequence does not contain a sync sample.");
Util.scaleLargeTimestampsInPlace(timestamps, C.MICROS_PER_SECOND, track.timescale);
return new TrackSampleTable(offsets, sizes, maximumSize, timestamps, flags);
} }
return new TrackSampleTable(editedOffsets, editedSizes, editedMaximumSize, editedTimestamps, return new TrackSampleTable(editedOffsets, editedSizes, editedMaximumSize, editedTimestamps,
...@@ -779,7 +791,7 @@ import java.util.List; ...@@ -779,7 +791,7 @@ import java.util.List;
* *
* @param edtsAtom edts (edit box) atom to decode. * @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 * @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<long[], long[]> parseEdts(Atom.ContainerAtom edtsAtom) { private static Pair<long[], long[]> parseEdts(Atom.ContainerAtom edtsAtom) {
Atom.LeafAtom elst; Atom.LeafAtom elst;
...@@ -1060,8 +1072,8 @@ import java.util.List; ...@@ -1060,8 +1072,8 @@ import java.util.List;
Assertions.checkArgument(childAtomSize > 0, "childAtomSize should be positive"); Assertions.checkArgument(childAtomSize > 0, "childAtomSize should be positive");
int childAtomType = parent.readInt(); int childAtomType = parent.readInt();
if (childAtomType == Atom.TYPE_sinf) { if (childAtomType == Atom.TYPE_sinf) {
Pair<Integer, TrackEncryptionBox> result = parseSinfFromParent(parent, childPosition, Pair<Integer, TrackEncryptionBox> result = parseCommonEncryptionSinfFromParent(parent,
childAtomSize); childPosition, childAtomSize);
if (result != null) { if (result != null) {
return result; return result;
} }
...@@ -1071,8 +1083,8 @@ import java.util.List; ...@@ -1071,8 +1083,8 @@ import java.util.List;
return null; return null;
} }
private static Pair<Integer, TrackEncryptionBox> parseSinfFromParent(ParsableByteArray parent, /* package */ static Pair<Integer, TrackEncryptionBox> parseCommonEncryptionSinfFromParent(
int position, int size) { ParsableByteArray parent, int position, int size) {
int childPosition = position + Atom.HEADER_SIZE; int childPosition = position + Atom.HEADER_SIZE;
int schemeInformationBoxPosition = C.POSITION_UNSET; int schemeInformationBoxPosition = C.POSITION_UNSET;
int schemeInformationBoxSize = 0; int schemeInformationBoxSize = 0;
...@@ -1086,7 +1098,7 @@ import java.util.List; ...@@ -1086,7 +1098,7 @@ import java.util.List;
dataFormat = parent.readInt(); dataFormat = parent.readInt();
} else if (childAtomType == Atom.TYPE_schm) { } else if (childAtomType == Atom.TYPE_schm) {
parent.skipBytes(4); parent.skipBytes(4);
// scheme_type field. Defined in ISO/IEC 23001-7:2016, section 4.1. // Common encryption scheme_type values are defined in ISO/IEC 23001-7:2016, section 4.1.
schemeType = parent.readString(4); schemeType = parent.readString(4);
} else if (childAtomType == Atom.TYPE_schi) { } else if (childAtomType == Atom.TYPE_schi) {
schemeInformationBoxPosition = childPosition; schemeInformationBoxPosition = childPosition;
...@@ -1095,7 +1107,8 @@ import java.util.List; ...@@ -1095,7 +1107,8 @@ import java.util.List;
childPosition += childAtomSize; childPosition += childAtomSize;
} }
if (schemeType != null) { if (C.CENC_TYPE_cenc.equals(schemeType) || C.CENC_TYPE_cbc1.equals(schemeType)
|| C.CENC_TYPE_cens.equals(schemeType) || C.CENC_TYPE_cbcs.equals(schemeType)) {
Assertions.checkArgument(dataFormat != null, "frma atom is mandatory"); Assertions.checkArgument(dataFormat != null, "frma atom is mandatory");
Assertions.checkArgument(schemeInformationBoxPosition != C.POSITION_UNSET, Assertions.checkArgument(schemeInformationBoxPosition != C.POSITION_UNSET,
"schi atom is mandatory"); "schi atom is mandatory");
......
...@@ -74,7 +74,7 @@ public final class FragmentedMp4Extractor implements Extractor { ...@@ -74,7 +74,7 @@ public final class FragmentedMp4Extractor implements Extractor {
@Retention(RetentionPolicy.SOURCE) @Retention(RetentionPolicy.SOURCE)
@IntDef(flag = true, value = {FLAG_WORKAROUND_EVERY_VIDEO_FRAME_IS_SYNC_FRAME, @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_WORKAROUND_IGNORE_TFDT_BOX, FLAG_ENABLE_EMSG_TRACK, FLAG_ENABLE_CEA608_TRACK,
FLAG_SIDELOADED}) FLAG_SIDELOADED, FLAG_WORKAROUND_IGNORE_EDIT_LISTS})
public @interface Flags {} public @interface Flags {}
/** /**
* Flag to work around an issue in some video streams where every frame is marked as a sync frame. * Flag to work around an issue in some video streams where every frame is marked as a sync frame.
...@@ -103,6 +103,10 @@ public final class FragmentedMp4Extractor implements Extractor { ...@@ -103,6 +103,10 @@ public final class FragmentedMp4Extractor implements Extractor {
* container. * container.
*/ */
private static final int FLAG_SIDELOADED = 16; 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 String TAG = "FragmentedMp4Extractor";
private static final int SAMPLE_GROUP_TYPE_seig = Util.getIntegerCodeForString("seig"); private static final int SAMPLE_GROUP_TYPE_seig = Util.getIntegerCodeForString("seig");
...@@ -426,7 +430,7 @@ public final class FragmentedMp4Extractor implements Extractor { ...@@ -426,7 +430,7 @@ public final class FragmentedMp4Extractor implements Extractor {
Atom.ContainerAtom atom = moov.containerChildren.get(i); Atom.ContainerAtom atom = moov.containerChildren.get(i);
if (atom.type == Atom.TYPE_trak) { if (atom.type == Atom.TYPE_trak) {
Track track = AtomParsers.parseTrak(atom, moov.getLeafAtomOfType(Atom.TYPE_mvhd), duration, 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) { if (track != null) {
tracks.put(track.id, track); tracks.put(track.id, track);
} }
......
...@@ -28,6 +28,7 @@ import com.google.android.exoplayer2.extractor.PositionHolder; ...@@ -28,6 +28,7 @@ import com.google.android.exoplayer2.extractor.PositionHolder;
import com.google.android.exoplayer2.extractor.SeekMap; import com.google.android.exoplayer2.extractor.SeekMap;
import com.google.android.exoplayer2.extractor.TrackOutput; import com.google.android.exoplayer2.extractor.TrackOutput;
import com.google.android.exoplayer2.extractor.mp4.Atom.ContainerAtom; 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.metadata.Metadata;
import com.google.android.exoplayer2.util.Assertions; import com.google.android.exoplayer2.util.Assertions;
import com.google.android.exoplayer2.util.NalUnitUtil; import com.google.android.exoplayer2.util.NalUnitUtil;
...@@ -58,6 +59,17 @@ public final class Mp4Extractor implements Extractor, SeekMap { ...@@ -58,6 +59,17 @@ public final class Mp4Extractor implements Extractor, SeekMap {
}; };
/** /**
* 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. * Parser states.
*/ */
@Retention(RetentionPolicy.SOURCE) @Retention(RetentionPolicy.SOURCE)
...@@ -76,6 +88,8 @@ public final class Mp4Extractor implements Extractor, SeekMap { ...@@ -76,6 +88,8 @@ public final class Mp4Extractor implements Extractor, SeekMap {
*/ */
private static final long RELOAD_MINIMUM_SEEK_DISTANCE = 256 * 1024; private static final long RELOAD_MINIMUM_SEEK_DISTANCE = 256 * 1024;
private final @Flags int flags;
// Temporary arrays. // Temporary arrays.
private final ParsableByteArray nalStartCode; private final ParsableByteArray nalStartCode;
private final ParsableByteArray nalLength; private final ParsableByteArray nalLength;
...@@ -98,7 +112,21 @@ public final class Mp4Extractor implements Extractor, SeekMap { ...@@ -98,7 +112,21 @@ public final class Mp4Extractor implements Extractor, SeekMap {
private long durationUs; private long durationUs;
private boolean isQuickTime; private boolean isQuickTime;
/**
* Creates a new extractor for unfragmented MP4 streams.
*/
public Mp4Extractor() { 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); atomHeader = new ParsableByteArray(Atom.LONG_HEADER_SIZE);
containerAtoms = new Stack<>(); containerAtoms = new Stack<>();
nalStartCode = new ParsableByteArray(NalUnitUtil.NAL_START_CODE); nalStartCode = new ParsableByteArray(NalUnitUtil.NAL_START_CODE);
...@@ -345,7 +373,7 @@ public final class Mp4Extractor implements Extractor, SeekMap { ...@@ -345,7 +373,7 @@ public final class Mp4Extractor implements Extractor, SeekMap {
} }
Track track = AtomParsers.parseTrak(atom, moov.getLeafAtomOfType(Atom.TYPE_mvhd), 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) { if (track == null) {
continue; continue;
} }
......
...@@ -81,12 +81,12 @@ public final class Track { ...@@ -81,12 +81,12 @@ public final class Track {
/** /**
* Durations of edit list segments in the movie timescale. Null if there is no edit list. * 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. * 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 * 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 { ...@@ -99,7 +99,7 @@ public final class Track {
public Track(int id, int type, long timescale, long movieTimescale, long durationUs, public Track(int id, int type, long timescale, long movieTimescale, long durationUs,
Format format, @Transformation int sampleTransformation, Format format, @Transformation int sampleTransformation,
@Nullable TrackEncryptionBox[] sampleDescriptionEncryptionBoxes, int nalUnitLengthFieldLength, @Nullable TrackEncryptionBox[] sampleDescriptionEncryptionBoxes, int nalUnitLengthFieldLength,
long[] editListDurations, long[] editListMediaTimes) { @Nullable long[] editListDurations, @Nullable long[] editListMediaTimes) {
this.id = id; this.id = id;
this.type = type; this.type = type;
this.timescale = timescale; this.timescale = timescale;
......
...@@ -23,6 +23,7 @@ import android.media.MediaCrypto; ...@@ -23,6 +23,7 @@ import android.media.MediaCrypto;
import android.media.MediaFormat; import android.media.MediaFormat;
import android.os.Looper; import android.os.Looper;
import android.os.SystemClock; import android.os.SystemClock;
import android.support.annotation.IntDef;
import android.support.annotation.Nullable; import android.support.annotation.Nullable;
import android.util.Log; import android.util.Log;
import com.google.android.exoplayer2.BaseRenderer; import com.google.android.exoplayer2.BaseRenderer;
...@@ -43,6 +44,9 @@ import com.google.android.exoplayer2.util.Assertions; ...@@ -43,6 +44,9 @@ import com.google.android.exoplayer2.util.Assertions;
import com.google.android.exoplayer2.util.NalUnitUtil; import com.google.android.exoplayer2.util.NalUnitUtil;
import com.google.android.exoplayer2.util.TraceUtil; import com.google.android.exoplayer2.util.TraceUtil;
import com.google.android.exoplayer2.util.Util; import com.google.android.exoplayer2.util.Util;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.nio.ByteBuffer; import java.nio.ByteBuffer;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.List; import java.util.List;
...@@ -158,9 +162,26 @@ public abstract class MediaCodecRenderer extends BaseRenderer { ...@@ -158,9 +162,26 @@ public abstract class MediaCodecRenderer extends BaseRenderer {
*/ */
private static final int REINITIALIZATION_STATE_WAIT_END_OF_STREAM = 2; private static final int REINITIALIZATION_STATE_WAIT_END_OF_STREAM = 2;
@Retention(RetentionPolicy.SOURCE)
@IntDef({ADAPTATION_WORKAROUND_MODE_NEVER, ADAPTATION_WORKAROUND_MODE_SAME_RESOLUTION,
ADAPTATION_WORKAROUND_MODE_ALWAYS})
private @interface AdaptationWorkaroundMode {}
/**
* The adaptation workaround is never used.
*/
private static final int ADAPTATION_WORKAROUND_MODE_NEVER = 0;
/**
* The adaptation workaround is used when adapting between formats of the same resolution only.
*/
private static final int ADAPTATION_WORKAROUND_MODE_SAME_RESOLUTION = 1;
/**
* The adaptation workaround is always used when adapting between formats.
*/
private static final int ADAPTATION_WORKAROUND_MODE_ALWAYS = 2;
/** /**
* H.264/AVC buffer to queue when using the adaptation workaround (see * H.264/AVC buffer to queue when using the adaptation workaround (see
* {@link #codecNeedsAdaptationWorkaround(String)}. Consists of three NAL units with start codes: * {@link #codecAdaptationWorkaroundMode(String)}. Consists of three NAL units with start codes:
* Baseline sequence/picture parameter sets and a 32 * 32 pixel IDR slice. This stream can be * Baseline sequence/picture parameter sets and a 32 * 32 pixel IDR slice. This stream can be
* queued to force a resolution change when adapting to a new format. * queued to force a resolution change when adapting to a new format.
*/ */
...@@ -182,9 +203,9 @@ public abstract class MediaCodecRenderer extends BaseRenderer { ...@@ -182,9 +203,9 @@ public abstract class MediaCodecRenderer extends BaseRenderer {
private DrmSession<FrameworkMediaCrypto> pendingDrmSession; private DrmSession<FrameworkMediaCrypto> pendingDrmSession;
private MediaCodec codec; private MediaCodec codec;
private MediaCodecInfo codecInfo; private MediaCodecInfo codecInfo;
private @AdaptationWorkaroundMode int codecAdaptationWorkaroundMode;
private boolean codecNeedsDiscardToSpsWorkaround; private boolean codecNeedsDiscardToSpsWorkaround;
private boolean codecNeedsFlushWorkaround; private boolean codecNeedsFlushWorkaround;
private boolean codecNeedsAdaptationWorkaround;
private boolean codecNeedsEosPropagationWorkaround; private boolean codecNeedsEosPropagationWorkaround;
private boolean codecNeedsEosFlushWorkaround; private boolean codecNeedsEosFlushWorkaround;
private boolean codecNeedsEosOutputExceptionWorkaround; private boolean codecNeedsEosOutputExceptionWorkaround;
...@@ -355,9 +376,9 @@ public abstract class MediaCodecRenderer extends BaseRenderer { ...@@ -355,9 +376,9 @@ public abstract class MediaCodecRenderer extends BaseRenderer {
} }
String codecName = codecInfo.name; String codecName = codecInfo.name;
codecAdaptationWorkaroundMode = codecAdaptationWorkaroundMode(codecName);
codecNeedsDiscardToSpsWorkaround = codecNeedsDiscardToSpsWorkaround(codecName, format); codecNeedsDiscardToSpsWorkaround = codecNeedsDiscardToSpsWorkaround(codecName, format);
codecNeedsFlushWorkaround = codecNeedsFlushWorkaround(codecName); codecNeedsFlushWorkaround = codecNeedsFlushWorkaround(codecName);
codecNeedsAdaptationWorkaround = codecNeedsAdaptationWorkaround(codecName);
codecNeedsEosPropagationWorkaround = codecNeedsEosPropagationWorkaround(codecName); codecNeedsEosPropagationWorkaround = codecNeedsEosPropagationWorkaround(codecName);
codecNeedsEosFlushWorkaround = codecNeedsEosFlushWorkaround(codecName); codecNeedsEosFlushWorkaround = codecNeedsEosFlushWorkaround(codecName);
codecNeedsEosOutputExceptionWorkaround = codecNeedsEosOutputExceptionWorkaround(codecName); codecNeedsEosOutputExceptionWorkaround = codecNeedsEosOutputExceptionWorkaround(codecName);
...@@ -458,7 +479,7 @@ public abstract class MediaCodecRenderer extends BaseRenderer { ...@@ -458,7 +479,7 @@ public abstract class MediaCodecRenderer extends BaseRenderer {
codecReceivedBuffers = false; codecReceivedBuffers = false;
codecNeedsDiscardToSpsWorkaround = false; codecNeedsDiscardToSpsWorkaround = false;
codecNeedsFlushWorkaround = false; codecNeedsFlushWorkaround = false;
codecNeedsAdaptationWorkaround = false; codecAdaptationWorkaroundMode = ADAPTATION_WORKAROUND_MODE_NEVER;
codecNeedsEosPropagationWorkaround = false; codecNeedsEosPropagationWorkaround = false;
codecNeedsEosFlushWorkaround = false; codecNeedsEosFlushWorkaround = false;
codecNeedsMonoChannelCountWorkaround = false; codecNeedsMonoChannelCountWorkaround = false;
...@@ -802,8 +823,10 @@ public abstract class MediaCodecRenderer extends BaseRenderer { ...@@ -802,8 +823,10 @@ public abstract class MediaCodecRenderer extends BaseRenderer {
&& canReconfigureCodec(codec, codecInfo.adaptive, oldFormat, format)) { && canReconfigureCodec(codec, codecInfo.adaptive, oldFormat, format)) {
codecReconfigured = true; codecReconfigured = true;
codecReconfigurationState = RECONFIGURATION_STATE_WRITE_PENDING; codecReconfigurationState = RECONFIGURATION_STATE_WRITE_PENDING;
codecNeedsAdaptationWorkaroundBuffer = codecNeedsAdaptationWorkaround codecNeedsAdaptationWorkaroundBuffer =
&& format.width == oldFormat.width && format.height == oldFormat.height; codecAdaptationWorkaroundMode == ADAPTATION_WORKAROUND_MODE_ALWAYS
|| (codecAdaptationWorkaroundMode == ADAPTATION_WORKAROUND_MODE_SAME_RESOLUTION
&& format.width == oldFormat.width && format.height == oldFormat.height);
} else { } else {
if (codecReceivedBuffers) { if (codecReceivedBuffers) {
// Signal end of stream and wait for any final output buffers before re-initialization. // Signal end of stream and wait for any final output buffers before re-initialization.
...@@ -989,7 +1012,7 @@ public abstract class MediaCodecRenderer extends BaseRenderer { ...@@ -989,7 +1012,7 @@ public abstract class MediaCodecRenderer extends BaseRenderer {
*/ */
private void processOutputFormat() throws ExoPlaybackException { private void processOutputFormat() throws ExoPlaybackException {
MediaFormat format = codec.getOutputFormat(); MediaFormat format = codec.getOutputFormat();
if (codecNeedsAdaptationWorkaround if (codecAdaptationWorkaroundMode != ADAPTATION_WORKAROUND_MODE_NEVER
&& format.getInteger(MediaFormat.KEY_WIDTH) == ADAPTATION_WORKAROUND_SLICE_WIDTH_HEIGHT && format.getInteger(MediaFormat.KEY_WIDTH) == ADAPTATION_WORKAROUND_SLICE_WIDTH_HEIGHT
&& format.getInteger(MediaFormat.KEY_HEIGHT) == ADAPTATION_WORKAROUND_SLICE_WIDTH_HEIGHT) { && format.getInteger(MediaFormat.KEY_HEIGHT) == ADAPTATION_WORKAROUND_SLICE_WIDTH_HEIGHT) {
// We assume this format changed event was caused by the adaptation workaround. // We assume this format changed event was caused by the adaptation workaround.
...@@ -1122,22 +1145,30 @@ public abstract class MediaCodecRenderer extends BaseRenderer { ...@@ -1122,22 +1145,30 @@ public abstract class MediaCodecRenderer extends BaseRenderer {
} }
/** /**
* Returns whether the decoder is known to get stuck during some adaptations where the resolution * Returns a mode that specifies when the adaptation workaround should be enabled.
* does not change.
* <p> * <p>
* If true is returned, the renderer will work around the issue by queueing and discarding a blank * When enabled, the workaround queues and discards a blank frame with a resolution whose width
* frame at a different resolution, which resets the codec's internal state. * and height both equal {@link #ADAPTATION_WORKAROUND_SLICE_WIDTH_HEIGHT}, to reset the codec's
* internal state when a format change occurs.
* <p> * <p>
* See [Internal: b/27807182]. * See [Internal: b/27807182].
* See <a href="https://github.com/google/ExoPlayer/issues/3257">GitHub issue #3257</a>.
* *
* @param name The name of the decoder. * @param name The name of the decoder.
* @return True if the decoder is known to get stuck during some adaptations. * @return The mode specifying when the adaptation workaround should be enabled.
*/ */
private static boolean codecNeedsAdaptationWorkaround(String name) { private @AdaptationWorkaroundMode int codecAdaptationWorkaroundMode(String name) {
return Util.SDK_INT < 24 if (Util.SDK_INT <= 24 && "OMX.Exynos.avc.dec.secure".equals(name)
&& (Util.MODEL.startsWith("SM-T585") || Util.MODEL.startsWith("SM-A520"))) {
return ADAPTATION_WORKAROUND_MODE_ALWAYS;
} else if (Util.SDK_INT < 24
&& ("OMX.Nvidia.h264.decode".equals(name) || "OMX.Nvidia.h264.decode.secure".equals(name)) && ("OMX.Nvidia.h264.decode".equals(name) || "OMX.Nvidia.h264.decode.secure".equals(name))
&& ("flounder".equals(Util.DEVICE) || "flounder_lte".equals(Util.DEVICE) && ("flounder".equals(Util.DEVICE) || "flounder_lte".equals(Util.DEVICE)
|| "grouper".equals(Util.DEVICE) || "tilapia".equals(Util.DEVICE)); || "grouper".equals(Util.DEVICE) || "tilapia".equals(Util.DEVICE))) {
return ADAPTATION_WORKAROUND_MODE_SAME_RESOLUTION;
} else {
return ADAPTATION_WORKAROUND_MODE_NEVER;
}
} }
/** /**
......
...@@ -973,9 +973,11 @@ public class MediaCodecVideoRenderer extends MediaCodecRenderer { ...@@ -973,9 +973,11 @@ public class MediaCodecVideoRenderer extends MediaCodecRenderer {
* If true is returned then we fall back to releasing and re-instantiating the codec instead. * If true is returned then we fall back to releasing and re-instantiating the codec instead.
*/ */
private static boolean codecNeedsSetOutputSurfaceWorkaround(String name) { private static boolean codecNeedsSetOutputSurfaceWorkaround(String name) {
// Work around https://github.com/google/ExoPlayer/issues/3236 // Work around https://github.com/google/ExoPlayer/issues/3236 and
return ("deb".equals(Util.DEVICE) || "flo".equals(Util.DEVICE)) // https://github.com/google/ExoPlayer/issues/3355.
&& "OMX.qcom.video.decoder.avc".equals(name); return (("deb".equals(Util.DEVICE) || "flo".equals(Util.DEVICE))
&& "OMX.qcom.video.decoder.avc".equals(name))
|| ("tcl_eu".equals(Util.DEVICE) && "OMX.MTK.VIDEO.DECODER.AVC".equals(name));
} }
/** /**
......
...@@ -173,7 +173,7 @@ public class DefaultDashChunkSource implements DashChunkSource { ...@@ -173,7 +173,7 @@ public class DefaultDashChunkSource implements DashChunkSource {
} }
@Override @Override
public final void getNextChunk(MediaChunk previous, long playbackPositionUs, ChunkHolder out) { public void getNextChunk(MediaChunk previous, long playbackPositionUs, ChunkHolder out) {
if (fatalError != null) { if (fatalError != null) {
return; return;
} }
...@@ -300,7 +300,7 @@ public class DefaultDashChunkSource implements DashChunkSource { ...@@ -300,7 +300,7 @@ public class DefaultDashChunkSource implements DashChunkSource {
trackSelection.indexOf(chunk.trackFormat), e); trackSelection.indexOf(chunk.trackFormat), e);
} }
// Private methods. // Internal methods.
private ArrayList<Representation> getRepresentations() { private ArrayList<Representation> getRepresentations() {
List<AdaptationSet> manifestAdapationSets = manifest.getPeriod(periodIndex).adaptationSets; List<AdaptationSet> manifestAdapationSets = manifest.getPeriod(periodIndex).adaptationSets;
...@@ -319,7 +319,7 @@ public class DefaultDashChunkSource implements DashChunkSource { ...@@ -319,7 +319,7 @@ public class DefaultDashChunkSource implements DashChunkSource {
} }
} }
private static Chunk newInitializationChunk(RepresentationHolder representationHolder, protected static Chunk newInitializationChunk(RepresentationHolder representationHolder,
DataSource dataSource, Format trackFormat, int trackSelectionReason, DataSource dataSource, Format trackFormat, int trackSelectionReason,
Object trackSelectionData, RangedUri initializationUri, RangedUri indexUri) { Object trackSelectionData, RangedUri initializationUri, RangedUri indexUri) {
RangedUri requestUri; RangedUri requestUri;
...@@ -340,7 +340,7 @@ public class DefaultDashChunkSource implements DashChunkSource { ...@@ -340,7 +340,7 @@ public class DefaultDashChunkSource implements DashChunkSource {
trackSelectionReason, trackSelectionData, representationHolder.extractorWrapper); trackSelectionReason, trackSelectionData, representationHolder.extractorWrapper);
} }
private static Chunk newMediaChunk(RepresentationHolder representationHolder, protected static Chunk newMediaChunk(RepresentationHolder representationHolder,
DataSource dataSource, int trackType, Format trackFormat, int trackSelectionReason, DataSource dataSource, int trackType, Format trackFormat, int trackSelectionReason,
Object trackSelectionData, int firstSegmentNum, int maxSegmentCount) { Object trackSelectionData, int firstSegmentNum, int maxSegmentCount) {
Representation representation = representationHolder.representation; Representation representation = representationHolder.representation;
......
...@@ -28,7 +28,7 @@ import java.util.List; ...@@ -28,7 +28,7 @@ import java.util.List;
import junit.framework.TestCase; import junit.framework.TestCase;
/** /**
* Test for {@link HlsMasterPlaylistParserTest} * Test for {@link HlsMasterPlaylistParserTest}.
*/ */
public class HlsMasterPlaylistParserTest extends TestCase { public class HlsMasterPlaylistParserTest extends TestCase {
......
...@@ -27,7 +27,7 @@ import java.util.Locale; ...@@ -27,7 +27,7 @@ import java.util.Locale;
import junit.framework.TestCase; import junit.framework.TestCase;
/** /**
* Test for {@link HlsMediaPlaylistParserTest} * Test for {@link HlsMediaPlaylistParserTest}.
*/ */
public class HlsMediaPlaylistParserTest extends TestCase { public class HlsMediaPlaylistParserTest extends TestCase {
......
...@@ -26,9 +26,12 @@ import com.google.android.exoplayer2.source.MediaPeriod; ...@@ -26,9 +26,12 @@ import com.google.android.exoplayer2.source.MediaPeriod;
import com.google.android.exoplayer2.source.MediaSource; import com.google.android.exoplayer2.source.MediaSource;
import com.google.android.exoplayer2.source.SinglePeriodTimeline; import com.google.android.exoplayer2.source.SinglePeriodTimeline;
import com.google.android.exoplayer2.source.hls.playlist.HlsMediaPlaylist; import com.google.android.exoplayer2.source.hls.playlist.HlsMediaPlaylist;
import com.google.android.exoplayer2.source.hls.playlist.HlsPlaylist;
import com.google.android.exoplayer2.source.hls.playlist.HlsPlaylistParser;
import com.google.android.exoplayer2.source.hls.playlist.HlsPlaylistTracker; import com.google.android.exoplayer2.source.hls.playlist.HlsPlaylistTracker;
import com.google.android.exoplayer2.upstream.Allocator; import com.google.android.exoplayer2.upstream.Allocator;
import com.google.android.exoplayer2.upstream.DataSource; import com.google.android.exoplayer2.upstream.DataSource;
import com.google.android.exoplayer2.upstream.ParsingLoadable;
import com.google.android.exoplayer2.util.Assertions; import com.google.android.exoplayer2.util.Assertions;
import java.io.IOException; import java.io.IOException;
import java.util.List; import java.util.List;
...@@ -52,6 +55,7 @@ public final class HlsMediaSource implements MediaSource, ...@@ -52,6 +55,7 @@ public final class HlsMediaSource implements MediaSource,
private final HlsDataSourceFactory dataSourceFactory; private final HlsDataSourceFactory dataSourceFactory;
private final int minLoadableRetryCount; private final int minLoadableRetryCount;
private final EventDispatcher eventDispatcher; private final EventDispatcher eventDispatcher;
private final ParsingLoadable.Parser<HlsPlaylist> playlistParser;
private HlsPlaylistTracker playlistTracker; private HlsPlaylistTracker playlistTracker;
private Listener sourceListener; private Listener sourceListener;
...@@ -72,9 +76,18 @@ public final class HlsMediaSource implements MediaSource, ...@@ -72,9 +76,18 @@ public final class HlsMediaSource implements MediaSource,
public HlsMediaSource(Uri manifestUri, HlsDataSourceFactory dataSourceFactory, public HlsMediaSource(Uri manifestUri, HlsDataSourceFactory dataSourceFactory,
int minLoadableRetryCount, Handler eventHandler, int minLoadableRetryCount, Handler eventHandler,
AdaptiveMediaSourceEventListener eventListener) { AdaptiveMediaSourceEventListener eventListener) {
this(manifestUri, dataSourceFactory, minLoadableRetryCount, eventHandler, eventListener,
new HlsPlaylistParser());
}
public HlsMediaSource(Uri manifestUri, HlsDataSourceFactory dataSourceFactory,
int minLoadableRetryCount, Handler eventHandler,
AdaptiveMediaSourceEventListener eventListener,
ParsingLoadable.Parser<HlsPlaylist> playlistParser) {
this.manifestUri = manifestUri; this.manifestUri = manifestUri;
this.dataSourceFactory = dataSourceFactory; this.dataSourceFactory = dataSourceFactory;
this.minLoadableRetryCount = minLoadableRetryCount; this.minLoadableRetryCount = minLoadableRetryCount;
this.playlistParser = playlistParser;
eventDispatcher = new EventDispatcher(eventHandler, eventListener); eventDispatcher = new EventDispatcher(eventHandler, eventListener);
} }
...@@ -82,7 +95,7 @@ public final class HlsMediaSource implements MediaSource, ...@@ -82,7 +95,7 @@ public final class HlsMediaSource implements MediaSource,
public void prepareSource(ExoPlayer player, boolean isTopLevelSource, Listener listener) { public void prepareSource(ExoPlayer player, boolean isTopLevelSource, Listener listener) {
Assertions.checkState(playlistTracker == null); Assertions.checkState(playlistTracker == null);
playlistTracker = new HlsPlaylistTracker(manifestUri, dataSourceFactory, eventDispatcher, playlistTracker = new HlsPlaylistTracker(manifestUri, dataSourceFactory, eventDispatcher,
minLoadableRetryCount, this); minLoadableRetryCount, this, playlistParser);
sourceListener = listener; sourceListener = listener;
playlistTracker.start(); playlistTracker.start();
} }
......
...@@ -17,6 +17,7 @@ package com.google.android.exoplayer2.source.hls.playlist; ...@@ -17,6 +17,7 @@ package com.google.android.exoplayer2.source.hls.playlist;
import com.google.android.exoplayer2.Format; import com.google.android.exoplayer2.Format;
import com.google.android.exoplayer2.util.MimeTypes; import com.google.android.exoplayer2.util.MimeTypes;
import java.util.ArrayList;
import java.util.Collections; import java.util.Collections;
import java.util.List; import java.util.List;
...@@ -109,6 +110,20 @@ public final class HlsMasterPlaylist extends HlsPlaylist { ...@@ -109,6 +110,20 @@ public final class HlsMasterPlaylist extends HlsPlaylist {
} }
/** /**
* Returns a copy of this playlist which includes only the renditions identified by the given
* urls.
*
* @param renditionUrls List of rendition urls.
* @return A copy of this playlist which includes only the renditions identified by the given
* urls.
*/
public HlsMasterPlaylist copy(List<String> renditionUrls) {
return new HlsMasterPlaylist(baseUri, tags, copyRenditionsList(variants, renditionUrls),
copyRenditionsList(audios, renditionUrls), copyRenditionsList(subtitles, renditionUrls),
muxedAudioFormat, muxedCaptionFormats);
}
/**
* Creates a playlist with a single variant. * Creates a playlist with a single variant.
* *
* @param variantUrl The url of the single variant. * @param variantUrl The url of the single variant.
...@@ -121,4 +136,15 @@ public final class HlsMasterPlaylist extends HlsPlaylist { ...@@ -121,4 +136,15 @@ public final class HlsMasterPlaylist extends HlsPlaylist {
emptyList, null, null); emptyList, null, null);
} }
private static List<HlsUrl> copyRenditionsList(List<HlsUrl> renditions, List<String> urls) {
List<HlsUrl> copiedRenditions = new ArrayList<>(urls.size());
for (int i = 0; i < renditions.size(); i++) {
HlsUrl rendition = renditions.get(i);
if (urls.contains(rendition.url)) {
copiedRenditions.add(rendition);
}
}
return copiedRenditions;
}
} }
...@@ -111,4 +111,5 @@ public class FakeMediaSource implements MediaSource { ...@@ -111,4 +111,5 @@ public class FakeMediaSource implements MediaSource {
} }
return new TrackGroupArray(trackGroups); return new TrackGroupArray(trackGroups);
} }
} }
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or sign in to comment