mirror of
https://github.com/androidx/media.git
synced 2025-04-30 06:46:50 +08:00
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:
parent
5f39b93d30
commit
12b7bbf803
@ -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);
|
||||
|
@ -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
|
||||
|
@ -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;
|
||||
}
|
||||
|
@ -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;
|
||||
}
|
||||
|
||||
|
@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -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;
|
||||
|
Loading…
x
Reference in New Issue
Block a user