mirror of
https://github.com/androidx/media.git
synced 2025-05-10 09:12:16 +08:00
Check #EXTM3U header is present in HLS playlists
Issue:#2301 ------------- Created by MOE: https://github.com/google/moe MOE_MIGRATED_REVID=144334062
This commit is contained in:
parent
706a6b83a9
commit
4a6a8553e9
@ -18,6 +18,7 @@ package com.google.android.exoplayer2.source.hls.playlist;
|
||||
import android.net.Uri;
|
||||
import com.google.android.exoplayer2.C;
|
||||
import com.google.android.exoplayer2.Format;
|
||||
import com.google.android.exoplayer2.ParserException;
|
||||
import java.io.ByteArrayInputStream;
|
||||
import java.io.IOException;
|
||||
import java.nio.charset.Charset;
|
||||
@ -29,9 +30,9 @@ import junit.framework.TestCase;
|
||||
*/
|
||||
public class HlsMasterPlaylistParserTest extends TestCase {
|
||||
|
||||
public void testParseMasterPlaylist() {
|
||||
Uri playlistUri = Uri.parse("https://example.com/test.m3u8");
|
||||
String playlistString = "#EXTM3U\n"
|
||||
private static final String PLAYLIST_URI = "https://example.com/test.m3u8";
|
||||
|
||||
private static final String MASTER_PLAYLIST = " #EXTM3U \n"
|
||||
+ "\n"
|
||||
+ "#EXT-X-STREAM-INF:BANDWIDTH=1280000,CODECS=\"mp4a.40.2,avc1.66.30\",RESOLUTION=304x128\n"
|
||||
+ "http://example.com/low.m3u8\n"
|
||||
@ -47,10 +48,13 @@ public class HlsMasterPlaylistParserTest extends TestCase {
|
||||
+ "\n"
|
||||
+ "#EXT-X-STREAM-INF:BANDWIDTH=65000,CODECS=\"mp4a.40.5\"\n"
|
||||
+ "http://example.com/audio-only.m3u8";
|
||||
ByteArrayInputStream inputStream = new ByteArrayInputStream(
|
||||
playlistString.getBytes(Charset.forName(C.UTF8_NAME)));
|
||||
try {
|
||||
HlsPlaylist playlist = new HlsPlaylistParser().parse(playlistUri, inputStream);
|
||||
|
||||
private static final String PLAYLIST_WITH_INVALID_HEADER = "#EXTMU3\n"
|
||||
+ "#EXT-X-STREAM-INF:BANDWIDTH=1280000,CODECS=\"mp4a.40.2,avc1.66.30\",RESOLUTION=304x128\n"
|
||||
+ "http://example.com/low.m3u8\n";
|
||||
|
||||
public void testParseMasterPlaylist() throws IOException{
|
||||
HlsPlaylist playlist = parsePlaylist(PLAYLIST_URI, MASTER_PLAYLIST);
|
||||
assertNotNull(playlist);
|
||||
assertEquals(HlsPlaylist.TYPE_MASTER, playlist.type);
|
||||
|
||||
@ -90,9 +94,22 @@ public class HlsMasterPlaylistParserTest extends TestCase {
|
||||
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);
|
||||
} catch (IOException exception) {
|
||||
fail(exception.getMessage());
|
||||
}
|
||||
|
||||
public void testPlaylistWithInvalidHeader() throws IOException {
|
||||
try {
|
||||
parsePlaylist(PLAYLIST_URI, PLAYLIST_WITH_INVALID_HEADER);
|
||||
fail("Expected exception not thrown.");
|
||||
} catch (ParserException e) {
|
||||
// Expected due to invalid header.
|
||||
}
|
||||
}
|
||||
|
||||
private static HlsPlaylist parsePlaylist(String uri, String playlistString) throws IOException {
|
||||
Uri playlistUri = Uri.parse(uri);
|
||||
ByteArrayInputStream inputStream = new ByteArrayInputStream(
|
||||
playlistString.getBytes(Charset.forName(C.UTF8_NAME)));
|
||||
return new HlsPlaylistParser().parse(playlistUri, inputStream);
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -39,6 +39,22 @@ import java.util.regex.Pattern;
|
||||
*/
|
||||
public final class HlsPlaylistParser implements ParsingLoadable.Parser<HlsPlaylist> {
|
||||
|
||||
/**
|
||||
* Thrown if the input does not start with an HLS playlist header.
|
||||
*/
|
||||
public static final class UnrecognizedInputFormatException extends ParserException {
|
||||
|
||||
public final Uri inputUri;
|
||||
|
||||
public UnrecognizedInputFormatException(Uri inputUri) {
|
||||
super("Input does not start with the #EXTM3U header. Uri: " + inputUri);
|
||||
this.inputUri = inputUri;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private static final String PLAYLIST_HEADER = "#EXTM3U";
|
||||
|
||||
private static final String TAG_VERSION = "#EXT-X-VERSION";
|
||||
private static final String TAG_STREAM_INF = "#EXT-X-STREAM-INF";
|
||||
private static final String TAG_MEDIA = "#EXT-X-MEDIA";
|
||||
@ -97,6 +113,9 @@ public final class HlsPlaylistParser implements ParsingLoadable.Parser<HlsPlayli
|
||||
Queue<String> extraLines = new LinkedList<>();
|
||||
String line;
|
||||
try {
|
||||
if (!checkPlaylistHeader(reader)) {
|
||||
throw new UnrecognizedInputFormatException(uri);
|
||||
}
|
||||
while ((line = reader.readLine()) != null) {
|
||||
line = line.trim();
|
||||
if (line.isEmpty()) {
|
||||
@ -124,6 +143,35 @@ public final class HlsPlaylistParser implements ParsingLoadable.Parser<HlsPlayli
|
||||
throw new ParserException("Failed to parse the playlist, could not identify any tags.");
|
||||
}
|
||||
|
||||
private static boolean checkPlaylistHeader(BufferedReader reader) throws IOException {
|
||||
int last = reader.read();
|
||||
if (last == 0xEF) {
|
||||
if (reader.read() != 0xBB || reader.read() != 0xBF) {
|
||||
return false;
|
||||
}
|
||||
// The playlist contains a Byte Order Mark, which gets discarded.
|
||||
last = reader.read();
|
||||
}
|
||||
last = skipIgnorableWhitespace(reader, true, last);
|
||||
int playlistHeaderLength = PLAYLIST_HEADER.length();
|
||||
for (int i = 0; i < playlistHeaderLength; i++) {
|
||||
if (last != PLAYLIST_HEADER.charAt(i)) {
|
||||
return false;
|
||||
}
|
||||
last = reader.read();
|
||||
}
|
||||
last = skipIgnorableWhitespace(reader, false, last);
|
||||
return Util.isLinebreak(last);
|
||||
}
|
||||
|
||||
private static int skipIgnorableWhitespace(BufferedReader reader, boolean skipLinebreaks, int c)
|
||||
throws IOException {
|
||||
while (c != -1 && Character.isWhitespace(c) && (skipLinebreaks || !Util.isLinebreak(c))) {
|
||||
c = reader.read();
|
||||
}
|
||||
return c;
|
||||
}
|
||||
|
||||
private static HlsMasterPlaylist parseMasterPlaylist(LineIterator iterator, String baseUri)
|
||||
throws IOException {
|
||||
ArrayList<HlsMasterPlaylist.HlsUrl> variants = new ArrayList<>();
|
||||
|
@ -494,7 +494,7 @@ public final class ParsableByteArray {
|
||||
return null;
|
||||
}
|
||||
int lineLimit = position;
|
||||
while (lineLimit < limit && data[lineLimit] != '\n' && data[lineLimit] != '\r') {
|
||||
while (lineLimit < limit && !Util.isLinebreak(data[lineLimit])) {
|
||||
lineLimit++;
|
||||
}
|
||||
if (lineLimit - position >= 3 && data[position] == (byte) 0xEF
|
||||
|
@ -254,6 +254,16 @@ public final class Util {
|
||||
return value.getBytes(Charset.defaultCharset()); // UTF-8 is the default on Android.
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns whether the given character is a carriage return ('\r') or a line feed ('\n').
|
||||
*
|
||||
* @param c The character.
|
||||
* @return Whether the given character is a linebreak.
|
||||
*/
|
||||
public static boolean isLinebreak(int c) {
|
||||
return c == '\n' || c == '\r';
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts text to lower case using {@link Locale#US}.
|
||||
*
|
||||
|
Loading…
x
Reference in New Issue
Block a user