Commit 12b7bbf8 by aquilescanta Committed by Santiago Seifert

Improve HLS master playlist parsing

Adds a few unused fields to HlsUrl and moves things towards the Hls
reimplementation we are looking for. Also fixes a bug related to
asuming every getNextChunk().loadable == null being related to
reaching the live edge.

-------------
Created by MOE: https://github.com/google/moe
MOE_MIGRATED_REVID=132305206
parent 5f39b93d
...@@ -61,32 +61,32 @@ public class HlsMasterPlaylistParserTest extends TestCase { ...@@ -61,32 +61,32 @@ public class HlsMasterPlaylistParserTest extends TestCase {
assertEquals(5, variants.size()); assertEquals(5, variants.size());
assertEquals(1280000, variants.get(0).format.bitrate); assertEquals(1280000, variants.get(0).format.bitrate);
assertNotNull(variants.get(0).codecs); assertNotNull(variants.get(0).format.codecs);
assertEquals("mp4a.40.2,avc1.66.30", variants.get(0).codecs); assertEquals("mp4a.40.2,avc1.66.30", variants.get(0).format.codecs);
assertEquals(304, variants.get(0).format.width); assertEquals(304, variants.get(0).format.width);
assertEquals(128, variants.get(0).format.height); assertEquals(128, variants.get(0).format.height);
assertEquals("http://example.com/low.m3u8", variants.get(0).url); assertEquals("http://example.com/low.m3u8", variants.get(0).url);
assertEquals(1280000, variants.get(1).format.bitrate); assertEquals(1280000, variants.get(1).format.bitrate);
assertNotNull(variants.get(1).codecs); assertNotNull(variants.get(1).format.codecs);
assertEquals("mp4a.40.2 , avc1.66.30 ", variants.get(1).codecs); assertEquals("mp4a.40.2 , avc1.66.30 ", variants.get(1).format.codecs);
assertEquals("http://example.com/spaces_in_codecs.m3u8", variants.get(1).url); assertEquals("http://example.com/spaces_in_codecs.m3u8", variants.get(1).url);
assertEquals(2560000, variants.get(2).format.bitrate); assertEquals(2560000, variants.get(2).format.bitrate);
assertEquals(null, variants.get(2).codecs); assertEquals(null, variants.get(2).format.codecs);
assertEquals(384, variants.get(2).format.width); assertEquals(384, variants.get(2).format.width);
assertEquals(160, variants.get(2).format.height); assertEquals(160, variants.get(2).format.height);
assertEquals("http://example.com/mid.m3u8", variants.get(2).url); assertEquals("http://example.com/mid.m3u8", variants.get(2).url);
assertEquals(7680000, variants.get(3).format.bitrate); assertEquals(7680000, variants.get(3).format.bitrate);
assertEquals(null, variants.get(3).codecs); assertEquals(null, variants.get(3).format.codecs);
assertEquals(Format.NO_VALUE, variants.get(3).format.width); assertEquals(Format.NO_VALUE, variants.get(3).format.width);
assertEquals(Format.NO_VALUE, variants.get(3).format.height); assertEquals(Format.NO_VALUE, variants.get(3).format.height);
assertEquals("http://example.com/hi.m3u8", variants.get(3).url); assertEquals("http://example.com/hi.m3u8", variants.get(3).url);
assertEquals(65000, variants.get(4).format.bitrate); assertEquals(65000, variants.get(4).format.bitrate);
assertNotNull(variants.get(4).codecs); assertNotNull(variants.get(4).format.codecs);
assertEquals("mp4a.40.5", variants.get(4).codecs); assertEquals("mp4a.40.5", variants.get(4).format.codecs);
assertEquals(Format.NO_VALUE, variants.get(4).format.width); assertEquals(Format.NO_VALUE, variants.get(4).format.width);
assertEquals(Format.NO_VALUE, variants.get(4).format.height); assertEquals(Format.NO_VALUE, variants.get(4).format.height);
assertEquals("http://example.com/audio-only.m3u8", variants.get(4).url); assertEquals("http://example.com/audio-only.m3u8", variants.get(4).url);
......
...@@ -362,7 +362,7 @@ import java.util.Locale; ...@@ -362,7 +362,7 @@ import java.util.Locale;
} }
// This flag ensures the change of pid between streams does not affect the sample queues. // This flag ensures the change of pid between streams does not affect the sample queues.
int workaroundFlags = TsExtractor.WORKAROUND_MAP_BY_TYPE; int workaroundFlags = TsExtractor.WORKAROUND_MAP_BY_TYPE;
String codecs = variants[newVariantIndex].codecs; String codecs = variants[newVariantIndex].format.codecs;
if (!TextUtils.isEmpty(codecs)) { if (!TextUtils.isEmpty(codecs)) {
// Sometimes AAC and H264 streams are declared in TS chunks even though they don't really // Sometimes AAC and H264 streams are declared in TS chunks even though they don't really
// exist. If we know from the codec attribute that they don't exist, then we can explicitly // exist. If we know from the codec attribute that they don't exist, then we can explicitly
......
...@@ -39,7 +39,6 @@ import com.google.android.exoplayer2.upstream.Allocator; ...@@ -39,7 +39,6 @@ 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.Loader; import com.google.android.exoplayer2.upstream.Loader;
import com.google.android.exoplayer2.upstream.ParsingLoadable; import com.google.android.exoplayer2.upstream.ParsingLoadable;
import com.google.android.exoplayer2.util.MimeTypes;
import java.io.IOException; import java.io.IOException;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.IdentityHashMap; import java.util.IdentityHashMap;
...@@ -318,10 +317,8 @@ import java.util.List; ...@@ -318,10 +317,8 @@ import java.util.List;
String baseUri = playlist.baseUri; String baseUri = playlist.baseUri;
if (playlist instanceof HlsMediaPlaylist) { if (playlist instanceof HlsMediaPlaylist) {
Format format = Format.createContainerFormat("0", MimeTypes.APPLICATION_M3U8, null, null,
Format.NO_VALUE);
HlsMasterPlaylist.HlsUrl[] variants = new HlsMasterPlaylist.HlsUrl[] { HlsMasterPlaylist.HlsUrl[] variants = new HlsMasterPlaylist.HlsUrl[] {
new HlsMasterPlaylist.HlsUrl(playlist.baseUri, format, null)}; HlsMasterPlaylist.HlsUrl.createMediaPlaylistHlsUrl(playlist.baseUri)};
sampleStreamWrappers.add(buildSampleStreamWrapper(C.TRACK_TYPE_DEFAULT, baseUri, variants, sampleStreamWrappers.add(buildSampleStreamWrapper(C.TRACK_TYPE_DEFAULT, baseUri, variants,
null, null)); null, null));
return sampleStreamWrappers; return sampleStreamWrappers;
...@@ -393,7 +390,7 @@ import java.util.List; ...@@ -393,7 +390,7 @@ import java.util.List;
private static boolean variantHasExplicitCodecWithPrefix(HlsMasterPlaylist.HlsUrl variant, private static boolean variantHasExplicitCodecWithPrefix(HlsMasterPlaylist.HlsUrl variant,
String prefix) { String prefix) {
String codecs = variant.codecs; String codecs = variant.format.codecs;
if (TextUtils.isEmpty(codecs)) { if (TextUtils.isEmpty(codecs)) {
return false; return false;
} }
......
...@@ -324,8 +324,10 @@ import java.util.LinkedList; ...@@ -324,8 +324,10 @@ import java.util.LinkedList;
} }
if (loadable == null) { if (loadable == null) {
Assertions.checkState(retryInMs != C.TIME_UNSET && chunkSource.isLive()); if (retryInMs != C.TIME_UNSET) {
callback.onContinueLoadingRequiredInMs(this, retryInMs); Assertions.checkState(chunkSource.isLive());
callback.onContinueLoadingRequiredInMs(this, retryInMs);
}
return false; return false;
} }
......
...@@ -16,6 +16,8 @@ ...@@ -16,6 +16,8 @@
package com.google.android.exoplayer2.source.hls.playlist; 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 java.util.Collections; import java.util.Collections;
import java.util.List; import java.util.List;
...@@ -24,6 +26,36 @@ import java.util.List; ...@@ -24,6 +26,36 @@ import java.util.List;
*/ */
public final class HlsMasterPlaylist extends HlsPlaylist { public final class HlsMasterPlaylist extends HlsPlaylist {
/**
* Represents a url in an HLS master playlist.
*/
public static final class HlsUrl {
public final String name;
public final String url;
public final Format format;
public final Format videoFormat;
public final Format audioFormat;
public final Format[] textFormats;
public static HlsUrl createMediaPlaylistHlsUrl(String baseUri) {
Format format = Format.createContainerFormat("0", MimeTypes.APPLICATION_M3U8, null, null,
Format.NO_VALUE);
return new HlsUrl(null, baseUri, format, null, null, null);
}
public HlsUrl(String name, String url, Format format, Format videoFormat, Format audioFormat,
Format[] textFormats) {
this.name = name;
this.url = url;
this.format = format;
this.videoFormat = videoFormat;
this.audioFormat = audioFormat;
this.textFormats = textFormats;
}
}
public final List<HlsUrl> variants; public final List<HlsUrl> variants;
public final List<HlsUrl> audios; public final List<HlsUrl> audios;
public final List<HlsUrl> subtitles; public final List<HlsUrl> subtitles;
...@@ -41,21 +73,4 @@ public final class HlsMasterPlaylist extends HlsPlaylist { ...@@ -41,21 +73,4 @@ public final class HlsMasterPlaylist extends HlsPlaylist {
this.muxedCaptionFormat = muxedCaptionFormat; this.muxedCaptionFormat = muxedCaptionFormat;
} }
/**
* Represents a url in an HLS master playlist.
*/
public static final class HlsUrl {
public final String url;
public final Format format;
public final String codecs;
public HlsUrl(String url, Format format, String codecs) {
this.url = url;
this.format = format;
this.codecs = codecs;
}
}
} }
...@@ -62,6 +62,12 @@ public final class HlsPlaylistParser implements ParsingLoadable.Parser<HlsPlayli ...@@ -62,6 +62,12 @@ public final class HlsPlaylistParser implements ParsingLoadable.Parser<HlsPlayli
private static final String BOOLEAN_TRUE = "YES"; private static final String BOOLEAN_TRUE = "YES";
private static final String BOOLEAN_FALSE = "NO"; private static final String BOOLEAN_FALSE = "NO";
private static final Pattern REGEX_GROUP_ID = Pattern.compile("GROUP-ID=\"(.+?)\"");
private static final Pattern REGEX_VIDEO = Pattern.compile("VIDEO=\"(.+?)\"");
private static final Pattern REGEX_AUDIO = Pattern.compile("AUDIO=\"(.+?)\"");
private static final Pattern REGEX_CLOSED_CAPTIONS = Pattern.compile("CLOSED-CAPTIONS=\"(.+?)\"");
private static final Pattern REGEX_SUBTITLES = Pattern.compile("SUBTITLES=\"(.+?)\"");
private static final Pattern REGEX_BANDWIDTH = Pattern.compile("BANDWIDTH=(\\d+)\\b"); private static final Pattern REGEX_BANDWIDTH = Pattern.compile("BANDWIDTH=(\\d+)\\b");
private static final Pattern REGEX_CODECS = Pattern.compile("CODECS=\"(.+?)\""); private static final Pattern REGEX_CODECS = Pattern.compile("CODECS=\"(.+?)\"");
private static final Pattern REGEX_RESOLUTION = Pattern.compile("RESOLUTION=(\\d+x\\d+)"); private static final Pattern REGEX_RESOLUTION = Pattern.compile("RESOLUTION=(\\d+x\\d+)");
...@@ -125,101 +131,82 @@ public final class HlsPlaylistParser implements ParsingLoadable.Parser<HlsPlayli ...@@ -125,101 +131,82 @@ public final class HlsPlaylistParser implements ParsingLoadable.Parser<HlsPlayli
ArrayList<HlsMasterPlaylist.HlsUrl> variants = new ArrayList<>(); ArrayList<HlsMasterPlaylist.HlsUrl> variants = new ArrayList<>();
ArrayList<HlsMasterPlaylist.HlsUrl> audios = new ArrayList<>(); ArrayList<HlsMasterPlaylist.HlsUrl> audios = new ArrayList<>();
ArrayList<HlsMasterPlaylist.HlsUrl> subtitles = new ArrayList<>(); ArrayList<HlsMasterPlaylist.HlsUrl> subtitles = new ArrayList<>();
int bitrate = 0;
String codecs = null;
int width = Format.NO_VALUE;
int height = Format.NO_VALUE;
String name = null;
Format muxedAudioFormat = null; Format muxedAudioFormat = null;
Format muxedCaptionFormat = null; Format muxedCaptionFormat = null;
boolean expectingStreamInfUrl = false;
String line; String line;
while (iterator.hasNext()) { while (iterator.hasNext()) {
line = iterator.next(); line = iterator.next();
if (line.startsWith(TAG_MEDIA)) { if (line.startsWith(TAG_MEDIA)) {
boolean isDefault = parseBooleanAttribute(line, REGEX_DEFAULT, false); int selectionFlags = parseSelectionFlags(line);
boolean isForced = parseBooleanAttribute(line, REGEX_FORCED, false); String uri = parseOptionalStringAttr(line, REGEX_URI);
boolean isAutoselect = parseBooleanAttribute(line, REGEX_AUTOSELECT, String name = parseStringAttr(line, REGEX_NAME);
false); String language = parseOptionalStringAttr(line, REGEX_LANGUAGE);
int selectionFlags = (isDefault ? Format.SELECTION_FLAG_DEFAULT : 0) Format format;
| (isForced ? Format.SELECTION_FLAG_FORCED : 0) switch (parseStringAttr(line, REGEX_TYPE)) {
| (isAutoselect ? Format.SELECTION_FLAG_AUTOSELECT : 0); case TYPE_AUDIO:
String type = parseStringAttr(line, REGEX_TYPE); format = Format.createAudioContainerFormat(name, MimeTypes.APPLICATION_M3U8,
if (TYPE_CLOSED_CAPTIONS.equals(type)) { null, null, Format.NO_VALUE, Format.NO_VALUE, Format.NO_VALUE, null, selectionFlags,
String instreamId = parseStringAttr(line, REGEX_INSTREAM_ID); language);
if ("CC1".equals(instreamId)) { if (uri == null) {
// We assume all subtitles belong to the same group. muxedAudioFormat = format;
String captionName = parseStringAttr(line, REGEX_NAME); } else {
String language = parseOptionalStringAttr(line, REGEX_LANGUAGE); audios.add(new HlsMasterPlaylist.HlsUrl(name, uri, format, null, format, null));
muxedCaptionFormat = Format.createTextContainerFormat(captionName, }
MimeTypes.APPLICATION_M3U8, MimeTypes.APPLICATION_EIA608, null, Format.NO_VALUE, break;
selectionFlags, language); case TYPE_SUBTITLES:
} format = Format.createTextContainerFormat(name, MimeTypes.APPLICATION_M3U8,
} else if (TYPE_SUBTITLES.equals(type)) { MimeTypes.TEXT_VTT, null, Format.NO_VALUE, selectionFlags, language);
// We assume all subtitles belong to the same group. subtitles.add(new HlsMasterPlaylist.HlsUrl(name, uri, format, null, format, null));
String subtitleName = parseStringAttr(line, REGEX_NAME); break;
String uri = parseStringAttr(line, REGEX_URI); case TYPE_CLOSED_CAPTIONS:
String language = parseOptionalStringAttr(line, REGEX_LANGUAGE); if ("CC1".equals(parseOptionalStringAttr(line, REGEX_INSTREAM_ID))) {
Format format = Format.createTextContainerFormat(subtitleName, MimeTypes.APPLICATION_M3U8, muxedCaptionFormat = Format.createTextContainerFormat(name,
MimeTypes.TEXT_VTT, null, bitrate, selectionFlags, language); MimeTypes.APPLICATION_M3U8, MimeTypes.APPLICATION_EIA608, null, Format.NO_VALUE,
subtitles.add(new HlsMasterPlaylist.HlsUrl(uri, format, codecs)); selectionFlags, language);
} else if (TYPE_AUDIO.equals(type)) { }
// We assume all audios belong to the same group. break;
String uri = parseOptionalStringAttr(line, REGEX_URI); default:
String language = parseOptionalStringAttr(line, REGEX_LANGUAGE); // Do nothing.
String audioName = parseStringAttr(line, REGEX_NAME); break;
int audioBitrate = uri != null ? bitrate : Format.NO_VALUE;
Format format = Format.createAudioContainerFormat(audioName, MimeTypes.APPLICATION_M3U8,
null, null, audioBitrate, Format.NO_VALUE, Format.NO_VALUE, null, selectionFlags,
language);
if (uri != null) {
audios.add(new HlsMasterPlaylist.HlsUrl(uri, format, codecs));
} else {
muxedAudioFormat = format;
}
} }
} else if (line.startsWith(TAG_STREAM_INF)) { } else if (line.startsWith(TAG_STREAM_INF)) {
bitrate = parseIntAttr(line, REGEX_BANDWIDTH); int bitrate = parseIntAttr(line, REGEX_BANDWIDTH);
codecs = parseOptionalStringAttr(line, REGEX_CODECS); String codecs = parseOptionalStringAttr(line, REGEX_CODECS);
name = parseOptionalStringAttr(line, REGEX_NAME);
String resolutionString = parseOptionalStringAttr(line, REGEX_RESOLUTION); String resolutionString = parseOptionalStringAttr(line, REGEX_RESOLUTION);
int width;
int height;
if (resolutionString != null) { if (resolutionString != null) {
String[] widthAndHeight = resolutionString.split("x"); String[] widthAndHeight = resolutionString.split("x");
width = Integer.parseInt(widthAndHeight[0]); width = Integer.parseInt(widthAndHeight[0]);
if (width <= 0) {
// Width was invalid.
width = Format.NO_VALUE;
}
height = Integer.parseInt(widthAndHeight[1]); height = Integer.parseInt(widthAndHeight[1]);
if (height <= 0) { if (width <= 0 || height <= 0) {
// Height was invalid. // Resolution string is invalid.
width = Format.NO_VALUE;
height = Format.NO_VALUE; height = Format.NO_VALUE;
} }
} else { } else {
width = Format.NO_VALUE; width = Format.NO_VALUE;
height = Format.NO_VALUE; height = Format.NO_VALUE;
} }
expectingStreamInfUrl = true; line = iterator.next();
} else if (!line.startsWith("#") && expectingStreamInfUrl) { String name = Integer.toString(variants.size());
if (name == null) {
name = Integer.toString(variants.size());
}
Format format = Format.createVideoContainerFormat(name, MimeTypes.APPLICATION_M3U8, null, Format format = Format.createVideoContainerFormat(name, MimeTypes.APPLICATION_M3U8, null,
null, bitrate, width, height, Format.NO_VALUE, null); codecs, bitrate, width, height, Format.NO_VALUE, null);
variants.add(new HlsMasterPlaylist.HlsUrl(line, format, codecs)); variants.add(new HlsMasterPlaylist.HlsUrl(name, line, format, null, null, null));
bitrate = 0;
codecs = null;
name = null;
width = Format.NO_VALUE;
height = Format.NO_VALUE;
expectingStreamInfUrl = false;
} }
} }
return new HlsMasterPlaylist(baseUri, variants, audios, subtitles, muxedAudioFormat, return new HlsMasterPlaylist(baseUri, variants, audios, subtitles, muxedAudioFormat,
muxedCaptionFormat); muxedCaptionFormat);
} }
private static int parseSelectionFlags(String line) {
return (parseBooleanAttribute(line, REGEX_DEFAULT, false) ? Format.SELECTION_FLAG_DEFAULT : 0)
| (parseBooleanAttribute(line, REGEX_FORCED, false) ? Format.SELECTION_FLAG_FORCED : 0)
| (parseBooleanAttribute(line, REGEX_AUTOSELECT, false) ? Format.SELECTION_FLAG_AUTOSELECT
: 0);
}
private static HlsMediaPlaylist parseMediaPlaylist(LineIterator iterator, String baseUri) private static HlsMediaPlaylist parseMediaPlaylist(LineIterator iterator, String baseUri)
throws IOException { throws IOException {
int mediaSequence = 0; int mediaSequence = 0;
......
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