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
This commit is contained in:
aquilescanta 2016-09-06 04:39:45 -07:00 committed by Santiago Seifert
parent 5f39b93d30
commit 12b7bbf803
6 changed files with 102 additions and 101 deletions

View File

@ -61,32 +61,32 @@ public class HlsMasterPlaylistParserTest extends TestCase {
assertEquals(5, variants.size());
assertEquals(1280000, variants.get(0).format.bitrate);
assertNotNull(variants.get(0).codecs);
assertEquals("mp4a.40.2,avc1.66.30", variants.get(0).codecs);
assertNotNull(variants.get(0).format.codecs);
assertEquals("mp4a.40.2,avc1.66.30", variants.get(0).format.codecs);
assertEquals(304, variants.get(0).format.width);
assertEquals(128, variants.get(0).format.height);
assertEquals("http://example.com/low.m3u8", variants.get(0).url);
assertEquals(1280000, variants.get(1).format.bitrate);
assertNotNull(variants.get(1).codecs);
assertEquals("mp4a.40.2 , avc1.66.30 ", variants.get(1).codecs);
assertNotNull(variants.get(1).format.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(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(160, variants.get(2).format.height);
assertEquals("http://example.com/mid.m3u8", variants.get(2).url);
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.height);
assertEquals("http://example.com/hi.m3u8", variants.get(3).url);
assertEquals(65000, variants.get(4).format.bitrate);
assertNotNull(variants.get(4).codecs);
assertEquals("mp4a.40.5", variants.get(4).codecs);
assertNotNull(variants.get(4).format.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.height);
assertEquals("http://example.com/audio-only.m3u8", variants.get(4).url);

View File

@ -362,7 +362,7 @@ import java.util.Locale;
}
// This flag ensures the change of pid between streams does not affect the sample queues.
int workaroundFlags = TsExtractor.WORKAROUND_MAP_BY_TYPE;
String codecs = variants[newVariantIndex].codecs;
String codecs = variants[newVariantIndex].format.codecs;
if (!TextUtils.isEmpty(codecs)) {
// 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

View File

@ -39,7 +39,6 @@ import com.google.android.exoplayer2.upstream.Allocator;
import com.google.android.exoplayer2.upstream.DataSource;
import com.google.android.exoplayer2.upstream.Loader;
import com.google.android.exoplayer2.upstream.ParsingLoadable;
import com.google.android.exoplayer2.util.MimeTypes;
import java.io.IOException;
import java.util.ArrayList;
import java.util.IdentityHashMap;
@ -318,10 +317,8 @@ import java.util.List;
String baseUri = playlist.baseUri;
if (playlist instanceof HlsMediaPlaylist) {
Format format = Format.createContainerFormat("0", MimeTypes.APPLICATION_M3U8, null, null,
Format.NO_VALUE);
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,
null, null));
return sampleStreamWrappers;
@ -393,7 +390,7 @@ import java.util.List;
private static boolean variantHasExplicitCodecWithPrefix(HlsMasterPlaylist.HlsUrl variant,
String prefix) {
String codecs = variant.codecs;
String codecs = variant.format.codecs;
if (TextUtils.isEmpty(codecs)) {
return false;
}

View File

@ -324,8 +324,10 @@ import java.util.LinkedList;
}
if (loadable == null) {
Assertions.checkState(retryInMs != C.TIME_UNSET && chunkSource.isLive());
callback.onContinueLoadingRequiredInMs(this, retryInMs);
if (retryInMs != C.TIME_UNSET) {
Assertions.checkState(chunkSource.isLive());
callback.onContinueLoadingRequiredInMs(this, retryInMs);
}
return false;
}

View File

@ -16,6 +16,8 @@
package com.google.android.exoplayer2.source.hls.playlist;
import com.google.android.exoplayer2.Format;
import com.google.android.exoplayer2.util.MimeTypes;
import java.util.Collections;
import java.util.List;
@ -24,6 +26,36 @@ import java.util.List;
*/
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> audios;
public final List<HlsUrl> subtitles;
@ -41,21 +73,4 @@ public final class HlsMasterPlaylist extends HlsPlaylist {
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;
}
}
}

View File

@ -62,6 +62,12 @@ public final class HlsPlaylistParser implements ParsingLoadable.Parser<HlsPlayli
private static final String BOOLEAN_TRUE = "YES";
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_CODECS = Pattern.compile("CODECS=\"(.+?)\"");
private static final Pattern REGEX_RESOLUTION = Pattern.compile("RESOLUTION=(\\d+x\\d+)");
@ -125,101 +131,82 @@ public final class HlsPlaylistParser implements ParsingLoadable.Parser<HlsPlayli
ArrayList<HlsMasterPlaylist.HlsUrl> variants = new ArrayList<>();
ArrayList<HlsMasterPlaylist.HlsUrl> audios = 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 muxedCaptionFormat = null;
boolean expectingStreamInfUrl = false;
String line;
while (iterator.hasNext()) {
line = iterator.next();
if (line.startsWith(TAG_MEDIA)) {
boolean isDefault = parseBooleanAttribute(line, REGEX_DEFAULT, false);
boolean isForced = parseBooleanAttribute(line, REGEX_FORCED, false);
boolean isAutoselect = parseBooleanAttribute(line, REGEX_AUTOSELECT,
false);
int selectionFlags = (isDefault ? Format.SELECTION_FLAG_DEFAULT : 0)
| (isForced ? Format.SELECTION_FLAG_FORCED : 0)
| (isAutoselect ? Format.SELECTION_FLAG_AUTOSELECT : 0);
String type = parseStringAttr(line, REGEX_TYPE);
if (TYPE_CLOSED_CAPTIONS.equals(type)) {
String instreamId = parseStringAttr(line, REGEX_INSTREAM_ID);
if ("CC1".equals(instreamId)) {
// We assume all subtitles belong to the same group.
String captionName = parseStringAttr(line, REGEX_NAME);
String language = parseOptionalStringAttr(line, REGEX_LANGUAGE);
muxedCaptionFormat = Format.createTextContainerFormat(captionName,
MimeTypes.APPLICATION_M3U8, MimeTypes.APPLICATION_EIA608, null, Format.NO_VALUE,
selectionFlags, language);
}
} else if (TYPE_SUBTITLES.equals(type)) {
// We assume all subtitles belong to the same group.
String subtitleName = parseStringAttr(line, REGEX_NAME);
String uri = parseStringAttr(line, REGEX_URI);
String language = parseOptionalStringAttr(line, REGEX_LANGUAGE);
Format format = Format.createTextContainerFormat(subtitleName, MimeTypes.APPLICATION_M3U8,
MimeTypes.TEXT_VTT, null, bitrate, selectionFlags, language);
subtitles.add(new HlsMasterPlaylist.HlsUrl(uri, format, codecs));
} else if (TYPE_AUDIO.equals(type)) {
// We assume all audios belong to the same group.
String uri = parseOptionalStringAttr(line, REGEX_URI);
String language = parseOptionalStringAttr(line, REGEX_LANGUAGE);
String audioName = parseStringAttr(line, REGEX_NAME);
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;
}
int selectionFlags = parseSelectionFlags(line);
String uri = parseOptionalStringAttr(line, REGEX_URI);
String name = parseStringAttr(line, REGEX_NAME);
String language = parseOptionalStringAttr(line, REGEX_LANGUAGE);
Format format;
switch (parseStringAttr(line, REGEX_TYPE)) {
case TYPE_AUDIO:
format = Format.createAudioContainerFormat(name, MimeTypes.APPLICATION_M3U8,
null, null, Format.NO_VALUE, Format.NO_VALUE, Format.NO_VALUE, null, selectionFlags,
language);
if (uri == null) {
muxedAudioFormat = format;
} else {
audios.add(new HlsMasterPlaylist.HlsUrl(name, uri, format, null, format, null));
}
break;
case TYPE_SUBTITLES:
format = Format.createTextContainerFormat(name, MimeTypes.APPLICATION_M3U8,
MimeTypes.TEXT_VTT, null, Format.NO_VALUE, selectionFlags, language);
subtitles.add(new HlsMasterPlaylist.HlsUrl(name, uri, format, null, format, null));
break;
case TYPE_CLOSED_CAPTIONS:
if ("CC1".equals(parseOptionalStringAttr(line, REGEX_INSTREAM_ID))) {
muxedCaptionFormat = Format.createTextContainerFormat(name,
MimeTypes.APPLICATION_M3U8, MimeTypes.APPLICATION_EIA608, null, Format.NO_VALUE,
selectionFlags, language);
}
break;
default:
// Do nothing.
break;
}
} else if (line.startsWith(TAG_STREAM_INF)) {
bitrate = parseIntAttr(line, REGEX_BANDWIDTH);
codecs = parseOptionalStringAttr(line, REGEX_CODECS);
name = parseOptionalStringAttr(line, REGEX_NAME);
int bitrate = parseIntAttr(line, REGEX_BANDWIDTH);
String codecs = parseOptionalStringAttr(line, REGEX_CODECS);
String resolutionString = parseOptionalStringAttr(line, REGEX_RESOLUTION);
int width;
int height;
if (resolutionString != null) {
String[] widthAndHeight = resolutionString.split("x");
width = Integer.parseInt(widthAndHeight[0]);
if (width <= 0) {
// Width was invalid.
width = Format.NO_VALUE;
}
height = Integer.parseInt(widthAndHeight[1]);
if (height <= 0) {
// Height was invalid.
if (width <= 0 || height <= 0) {
// Resolution string is invalid.
width = Format.NO_VALUE;
height = Format.NO_VALUE;
}
} else {
width = Format.NO_VALUE;
height = Format.NO_VALUE;
}
expectingStreamInfUrl = true;
} else if (!line.startsWith("#") && expectingStreamInfUrl) {
if (name == null) {
name = Integer.toString(variants.size());
}
line = iterator.next();
String name = Integer.toString(variants.size());
Format format = Format.createVideoContainerFormat(name, MimeTypes.APPLICATION_M3U8, null,
null, bitrate, width, height, Format.NO_VALUE, null);
variants.add(new HlsMasterPlaylist.HlsUrl(line, format, codecs));
bitrate = 0;
codecs = null;
name = null;
width = Format.NO_VALUE;
height = Format.NO_VALUE;
expectingStreamInfUrl = false;
codecs, bitrate, width, height, Format.NO_VALUE, null);
variants.add(new HlsMasterPlaylist.HlsUrl(name, line, format, null, null, null));
}
}
return new HlsMasterPlaylist(baseUri, variants, audios, subtitles, muxedAudioFormat,
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)
throws IOException {
int mediaSequence = 0;