Commit 410fcdeb by Oliver Woodman

Merge HLS playlist parsers, make a single parser identify the

playlist type (master or media).

Issue: #155
parent f9f3b82d
......@@ -50,8 +50,9 @@ public class DemoUtil {
public static final int TYPE_DASH_VOD = 0;
public static final int TYPE_SS = 1;
public static final int TYPE_OTHER = 2;
public static final int TYPE_HLS_MASTER = 3;
public static final int TYPE_HLS_MEDIA = 4;
public static final int TYPE_DASH_LIVE = 3;
public static final int TYPE_DASH_LIVE_DVR = 4;
public static final int TYPE_HLS = 5;
public static final boolean EXPOSE_EXPERIMENTAL_FEATURES = false;
......
......@@ -59,7 +59,7 @@ package com.google.android.exoplayer.demo;
DemoUtil.TYPE_SS, false, false),
new Sample("Apple master playlist (HLS)", "uid:hls:applemaster",
"https://devimages.apple.com.edgekey.net/streaming/examples/bipbop_4x3/"
+ "bipbop_4x3_variant.m3u8", DemoUtil.TYPE_HLS_MASTER, false, false),
+ "bipbop_4x3_variant.m3u8", DemoUtil.TYPE_HLS, false, false),
new Sample("Dizzy (Misc)", "uid:misc:dizzy",
"http://html5demos.com/assets/dizzy.mp4", DemoUtil.TYPE_OTHER, false, false),
};
......@@ -137,13 +137,13 @@ package com.google.android.exoplayer.demo;
public static final Sample[] HLS = new Sample[] {
new Sample("Apple master playlist", "uid:hls:applemaster",
"https://devimages.apple.com.edgekey.net/streaming/examples/bipbop_4x3/"
+ "bipbop_4x3_variant.m3u8", DemoUtil.TYPE_HLS_MASTER, false, true),
+ "bipbop_4x3_variant.m3u8", DemoUtil.TYPE_HLS, false, true),
new Sample("Apple master playlist advanced", "uid:hls:applemasteradvanced",
"https://devimages.apple.com.edgekey.net/streaming/examples/bipbop_16x9/"
+ "bipbop_16x9_variant.m3u8", DemoUtil.TYPE_HLS_MASTER, false, true),
+ "bipbop_16x9_variant.m3u8", DemoUtil.TYPE_HLS, false, true),
new Sample("Apple single media playlist", "uid:hls:applesinglemedia",
"https://devimages.apple.com.edgekey.net/streaming/examples/bipbop_4x3/gear1/"
+ "prog_index.m3u8", DemoUtil.TYPE_HLS_MEDIA, false, true),
+ "prog_index.m3u8", DemoUtil.TYPE_HLS, false, true),
};
public static final Sample[] MISC = new Sample[] {
......
......@@ -185,9 +185,8 @@ public class FullPlayerActivity extends Activity implements SurfaceHolder.Callba
case DemoUtil.TYPE_DASH_VOD:
return new DashVodRendererBuilder(userAgent, contentUri.toString(), contentId,
new WidevineTestMediaDrmCallback(contentId), debugTextView);
case DemoUtil.TYPE_HLS_MASTER:
case DemoUtil.TYPE_HLS_MEDIA:
return new HlsRendererBuilder(userAgent, contentUri.toString(), contentId, contentType);
case DemoUtil.TYPE_HLS:
return new HlsRendererBuilder(userAgent, contentUri.toString(), contentId);
default:
return new DefaultRendererBuilder(this, contentUri, debugTextView);
}
......@@ -429,7 +428,7 @@ public class FullPlayerActivity extends Activity implements SurfaceHolder.Callba
}
}
//DemoPlayer.ClosedCaptioListener implementation
// DemoPlayer.ClosedCaptioListener implementation
@Override
public void onClosedCaption(List<ClosedCaption> closedCaptions) {
......
......@@ -19,14 +19,12 @@ import com.google.android.exoplayer.MediaCodecAudioTrackRenderer;
import com.google.android.exoplayer.MediaCodecUtil;
import com.google.android.exoplayer.MediaCodecVideoTrackRenderer;
import com.google.android.exoplayer.TrackRenderer;
import com.google.android.exoplayer.demo.DemoUtil;
import com.google.android.exoplayer.demo.full.player.DemoPlayer.RendererBuilder;
import com.google.android.exoplayer.demo.full.player.DemoPlayer.RendererBuilderCallback;
import com.google.android.exoplayer.hls.HlsChunkSource;
import com.google.android.exoplayer.hls.HlsMasterPlaylist;
import com.google.android.exoplayer.hls.HlsMasterPlaylistParser;
import com.google.android.exoplayer.hls.HlsPlaylist;
import com.google.android.exoplayer.hls.HlsPlaylistParser;
import com.google.android.exoplayer.hls.HlsSampleSource;
import com.google.android.exoplayer.hls.Variant;
import com.google.android.exoplayer.metadata.ClosedCaption;
import com.google.android.exoplayer.metadata.Eia608Parser;
import com.google.android.exoplayer.metadata.Id3Parser;
......@@ -39,48 +37,37 @@ import com.google.android.exoplayer.util.ManifestFetcher.ManifestCallback;
import com.google.android.exoplayer.util.MimeTypes;
import android.media.MediaCodec;
import android.net.Uri;
import java.io.IOException;
import java.util.Collections;
import java.util.List;
import java.util.Map;
/**
* A {@link RendererBuilder} for HLS.
*/
public class HlsRendererBuilder implements RendererBuilder, ManifestCallback<HlsMasterPlaylist> {
public class HlsRendererBuilder implements RendererBuilder, ManifestCallback<HlsPlaylist> {
private final String userAgent;
private final String url;
private final String contentId;
private final int contentType;
private DemoPlayer player;
private RendererBuilderCallback callback;
public HlsRendererBuilder(String userAgent, String url, String contentId, int contentType) {
public HlsRendererBuilder(String userAgent, String url, String contentId) {
this.userAgent = userAgent;
this.url = url;
this.contentId = contentId;
this.contentType = contentType;
}
@Override
public void buildRenderers(DemoPlayer player, RendererBuilderCallback callback) {
this.player = player;
this.callback = callback;
switch (contentType) {
case DemoUtil.TYPE_HLS_MASTER:
HlsMasterPlaylistParser parser = new HlsMasterPlaylistParser();
ManifestFetcher<HlsMasterPlaylist> mediaPlaylistFetcher =
new ManifestFetcher<HlsMasterPlaylist>(parser, contentId, url, userAgent);
mediaPlaylistFetcher.singleLoad(player.getMainHandler().getLooper(), this);
break;
case DemoUtil.TYPE_HLS_MEDIA:
onManifest(contentId, newSimpleMasterPlaylist(url));
break;
}
HlsPlaylistParser parser = new HlsPlaylistParser();
ManifestFetcher<HlsPlaylist> playlistFetcher =
new ManifestFetcher<HlsPlaylist>(parser, contentId, url, userAgent);
playlistFetcher.singleLoad(player.getMainHandler().getLooper(), this);
}
@Override
......@@ -89,11 +76,11 @@ public class HlsRendererBuilder implements RendererBuilder, ManifestCallback<Hls
}
@Override
public void onManifest(String contentId, HlsMasterPlaylist manifest) {
public void onManifest(String contentId, HlsPlaylist manifest) {
DefaultBandwidthMeter bandwidthMeter = new DefaultBandwidthMeter();
DataSource dataSource = new UriDataSource(userAgent, bandwidthMeter);
HlsChunkSource chunkSource = new HlsChunkSource(dataSource, manifest, bandwidthMeter, null,
HlsChunkSource chunkSource = new HlsChunkSource(dataSource, url, manifest, bandwidthMeter, null,
MediaCodecUtil.getDecoderInfo(MimeTypes.VIDEO_H264, false).adaptive);
HlsSampleSource sampleSource = new HlsSampleSource(chunkSource, true, 3);
MediaCodecVideoTrackRenderer videoRenderer = new MediaCodecVideoTrackRenderer(sampleSource,
......@@ -116,9 +103,4 @@ public class HlsRendererBuilder implements RendererBuilder, ManifestCallback<Hls
callback.onRenderers(null, null, renderers);
}
private HlsMasterPlaylist newSimpleMasterPlaylist(String mediaPlaylistUrl) {
return new HlsMasterPlaylist(Uri.parse(""),
Collections.singletonList(new Variant(0, mediaPlaylistUrl, 0, null, -1, -1)));
}
}
......@@ -19,15 +19,13 @@ import com.google.android.exoplayer.MediaCodecAudioTrackRenderer;
import com.google.android.exoplayer.MediaCodecUtil;
import com.google.android.exoplayer.MediaCodecVideoTrackRenderer;
import com.google.android.exoplayer.TrackRenderer;
import com.google.android.exoplayer.demo.DemoUtil;
import com.google.android.exoplayer.demo.full.player.DemoPlayer;
import com.google.android.exoplayer.demo.simple.SimplePlayerActivity.RendererBuilder;
import com.google.android.exoplayer.demo.simple.SimplePlayerActivity.RendererBuilderCallback;
import com.google.android.exoplayer.hls.HlsChunkSource;
import com.google.android.exoplayer.hls.HlsMasterPlaylist;
import com.google.android.exoplayer.hls.HlsMasterPlaylistParser;
import com.google.android.exoplayer.hls.HlsPlaylist;
import com.google.android.exoplayer.hls.HlsPlaylistParser;
import com.google.android.exoplayer.hls.HlsSampleSource;
import com.google.android.exoplayer.hls.Variant;
import com.google.android.exoplayer.upstream.DataSource;
import com.google.android.exoplayer.upstream.DefaultBandwidthMeter;
import com.google.android.exoplayer.upstream.UriDataSource;
......@@ -36,48 +34,37 @@ import com.google.android.exoplayer.util.ManifestFetcher.ManifestCallback;
import com.google.android.exoplayer.util.MimeTypes;
import android.media.MediaCodec;
import android.net.Uri;
import java.io.IOException;
import java.util.Collections;
/**
* A {@link RendererBuilder} for HLS.
*/
/* package */ class HlsRendererBuilder implements RendererBuilder,
ManifestCallback<HlsMasterPlaylist> {
ManifestCallback<HlsPlaylist> {
private final SimplePlayerActivity playerActivity;
private final String userAgent;
private final String url;
private final String contentId;
private final int playlistType;
private RendererBuilderCallback callback;
public HlsRendererBuilder(SimplePlayerActivity playerActivity, String userAgent, String url,
String contentId, int playlistType) {
String contentId) {
this.playerActivity = playerActivity;
this.userAgent = userAgent;
this.url = url;
this.contentId = contentId;
this.playlistType = playlistType;
}
@Override
public void buildRenderers(RendererBuilderCallback callback) {
this.callback = callback;
switch (playlistType) {
case DemoUtil.TYPE_HLS_MASTER:
HlsMasterPlaylistParser parser = new HlsMasterPlaylistParser();
ManifestFetcher<HlsMasterPlaylist> mediaPlaylistFetcher =
new ManifestFetcher<HlsMasterPlaylist>(parser, contentId, url, userAgent);
mediaPlaylistFetcher.singleLoad(playerActivity.getMainLooper(), this);
break;
case DemoUtil.TYPE_HLS_MEDIA:
onManifest(contentId, newSimpleMasterPlaylist(url));
break;
}
HlsPlaylistParser parser = new HlsPlaylistParser();
ManifestFetcher<HlsPlaylist> playlistFetcher =
new ManifestFetcher<HlsPlaylist>(parser, contentId, url, userAgent);
playlistFetcher.singleLoad(playerActivity.getMainLooper(), this);
}
@Override
......@@ -86,10 +73,11 @@ import java.util.Collections;
}
@Override
public void onManifest(String contentId, HlsMasterPlaylist manifest) {
public void onManifest(String contentId, HlsPlaylist manifest) {
DefaultBandwidthMeter bandwidthMeter = new DefaultBandwidthMeter();
DataSource dataSource = new UriDataSource(userAgent, bandwidthMeter);
HlsChunkSource chunkSource = new HlsChunkSource(dataSource, manifest, bandwidthMeter, null,
HlsChunkSource chunkSource = new HlsChunkSource(dataSource, url, manifest, bandwidthMeter, null,
MediaCodecUtil.getDecoderInfo(MimeTypes.VIDEO_H264, false).adaptive);
HlsSampleSource sampleSource = new HlsSampleSource(chunkSource, true, 2);
MediaCodecVideoTrackRenderer videoRenderer = new MediaCodecVideoTrackRenderer(sampleSource,
......@@ -103,9 +91,4 @@ import java.util.Collections;
callback.onRenderers(videoRenderer, audioRenderer);
}
private HlsMasterPlaylist newSimpleMasterPlaylist(String mediaPlaylistUrl) {
return new HlsMasterPlaylist(Uri.parse(""),
Collections.singletonList(new Variant(0, mediaPlaylistUrl, 0, null, -1, -1)));
}
}
......@@ -166,10 +166,8 @@ public class SimplePlayerActivity extends Activity implements SurfaceHolder.Call
contentId);
case DemoUtil.TYPE_DASH_VOD:
return new DashVodRendererBuilder(this, userAgent, contentUri.toString(), contentId);
case DemoUtil.TYPE_HLS_MASTER:
case DemoUtil.TYPE_HLS_MEDIA:
return new HlsRendererBuilder(this, userAgent, contentUri.toString(), contentId,
contentType);
case DemoUtil.TYPE_HLS:
return new HlsRendererBuilder(this, userAgent, contentUri.toString(), contentId);
default:
return new DefaultRendererBuilder(this, contentUri);
}
......
......@@ -23,6 +23,7 @@ import com.google.android.exoplayer.upstream.Aes128DataSource;
import com.google.android.exoplayer.upstream.BandwidthMeter;
import com.google.android.exoplayer.upstream.DataSource;
import com.google.android.exoplayer.upstream.DataSpec;
import com.google.android.exoplayer.util.Assertions;
import com.google.android.exoplayer.util.BitArray;
import com.google.android.exoplayer.util.Util;
......@@ -51,7 +52,7 @@ public class HlsChunkSource {
private final SamplePool samplePool = new TsExtractor.SamplePool();
private final DataSource upstreamDataSource;
private final HlsMediaPlaylistParser mediaPlaylistParser;
private final HlsPlaylistParser playlistParser;
private final Variant[] enabledVariants;
private final BandwidthMeter bandwidthMeter;
private final BitArray bitArray;
......@@ -73,21 +74,32 @@ public class HlsChunkSource {
/**
* @param dataSource A {@link DataSource} suitable for loading the media data.
* @param masterPlaylist The master playlist.
* @param playlistUrl The playlist URL.
* @param playlist The hls playlist.
* @param bandwidthMeter provides an estimate of the currently available bandwidth.
* @param variantIndices A subset of variant indices to consider, or null to consider all of the
* variants in the master playlist.
*/
public HlsChunkSource(DataSource dataSource, HlsMasterPlaylist masterPlaylist,
public HlsChunkSource(DataSource dataSource, String playlistUrl, HlsPlaylist playlist,
BandwidthMeter bandwidthMeter, int[] variantIndices, boolean enableAdaptive) {
this.upstreamDataSource = dataSource;
this.bandwidthMeter = bandwidthMeter;
this.enableAdaptive = enableAdaptive;
baseUri = masterPlaylist.baseUri;
baseUri = playlist.baseUri;
bitArray = new BitArray();
mediaPlaylistParser = new HlsMediaPlaylistParser();
enabledVariants = filterVariants(masterPlaylist, variantIndices);
lastMediaPlaylistLoadTimesMs = new long[enabledVariants.length];
playlistParser = new HlsPlaylistParser();
if (playlist.type == HlsPlaylist.TYPE_MEDIA) {
enabledVariants = new Variant[] {new Variant(0, playlistUrl, 0, null, -1, -1)};
mediaPlaylists = new HlsMediaPlaylist[] {(HlsMediaPlaylist) playlist};
} else {
Assertions.checkState(playlist.type == HlsPlaylist.TYPE_MASTER);
enabledVariants = filterVariants((HlsMasterPlaylist) playlist, variantIndices);
mediaPlaylists = new HlsMediaPlaylist[enabledVariants.length];
}
lastMediaPlaylistLoadTimesMs = new long[enabledVariants.length];
int maxWidth = -1;
int maxHeight = -1;
// Select the first variant from the master playlist that's enabled.
......@@ -391,9 +403,11 @@ public class HlsChunkSource {
@Override
protected void consume(BitArray data) throws IOException {
HlsMediaPlaylist mediaPlaylist = mediaPlaylistParser.parse(
HlsPlaylist playlist = playlistParser.parse(
new ByteArrayInputStream(data.getData(), 0, data.bytesLeft()), null, null,
playlistBaseUri);
Assertions.checkState(playlist.type == HlsPlaylist.TYPE_MEDIA);
HlsMediaPlaylist mediaPlaylist = (HlsMediaPlaylist) playlist;
mediaPlaylists[variantIndex] = mediaPlaylist;
lastMediaPlaylistLoadTimesMs[variantIndex] = SystemClock.elapsedRealtime();
live |= mediaPlaylist.live;
......
......@@ -22,13 +22,12 @@ import java.util.List;
/**
* Represents an HLS master playlist.
*/
public final class HlsMasterPlaylist {
public final class HlsMasterPlaylist extends HlsPlaylist {
public final Uri baseUri;
public final List<Variant> variants;
public HlsMasterPlaylist(Uri baseUri, List<Variant> variants) {
this.baseUri = baseUri;
super(baseUri, HlsPlaylist.TYPE_MASTER);
this.variants = variants;
}
......
/*
* Copyright (C) 2014 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.google.android.exoplayer.hls;
import com.google.android.exoplayer.util.ManifestParser;
import android.net.Uri;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.regex.Pattern;
/**
* HLS Master playlists parsing logic.
*/
public final class HlsMasterPlaylistParser implements ManifestParser<HlsMasterPlaylist> {
private static final String STREAM_INF_TAG = "#EXT-X-STREAM-INF";
private static final String BANDWIDTH_ATTR = "BANDWIDTH";
private static final String CODECS_ATTR = "CODECS";
private static final String RESOLUTION_ATTR = "RESOLUTION";
private static final Pattern BANDWIDTH_ATTR_REGEX =
Pattern.compile(BANDWIDTH_ATTR + "=(\\d+)\\b");
private static final Pattern CODECS_ATTR_REGEX =
Pattern.compile(CODECS_ATTR + "=\"(.+)\"");
private static final Pattern RESOLUTION_ATTR_REGEX =
Pattern.compile(RESOLUTION_ATTR + "=(\\d+x\\d+)");
@Override
public HlsMasterPlaylist parse(InputStream inputStream, String inputEncoding,
String contentId, Uri baseUri) throws IOException {
return parseMasterPlaylist(inputStream, inputEncoding, baseUri);
}
private static HlsMasterPlaylist parseMasterPlaylist(InputStream inputStream,
String inputEncoding, Uri baseUri) throws IOException {
BufferedReader reader = new BufferedReader((inputEncoding == null)
? new InputStreamReader(inputStream) : new InputStreamReader(inputStream, inputEncoding));
List<Variant> variants = new ArrayList<Variant>();
int bandwidth = 0;
String[] codecs = null;
int width = -1;
int height = -1;
int variantIndex = 0;
String line;
while ((line = reader.readLine()) != null) {
line = line.trim();
if (line.isEmpty()) {
continue;
}
if (line.startsWith(STREAM_INF_TAG)) {
bandwidth = HlsParserUtil.parseIntAttr(line, BANDWIDTH_ATTR_REGEX, BANDWIDTH_ATTR);
String codecsString = HlsParserUtil.parseOptionalStringAttr(line, CODECS_ATTR_REGEX);
if (codecsString != null) {
codecs = codecsString.split("(\\s*,\\s*)|(\\s*$)");
} else {
codecs = null;
}
String resolutionString = HlsParserUtil.parseOptionalStringAttr(line,
RESOLUTION_ATTR_REGEX);
if (resolutionString != null) {
String[] widthAndHeight = resolutionString.split("x");
width = Integer.parseInt(widthAndHeight[0]);
height = Integer.parseInt(widthAndHeight[1]);
} else {
width = -1;
height = -1;
}
} else if (!line.startsWith("#")) {
variants.add(new Variant(variantIndex++, line, bandwidth, codecs, width, height));
bandwidth = 0;
codecs = null;
width = -1;
height = -1;
}
}
return new HlsMasterPlaylist(baseUri, Collections.unmodifiableList(variants));
}
}
......@@ -22,7 +22,7 @@ import java.util.List;
/**
* Represents an HLS media playlist.
*/
public final class HlsMediaPlaylist {
public final class HlsMediaPlaylist extends HlsPlaylist {
/**
* Media segment reference.
......@@ -61,7 +61,6 @@ public final class HlsMediaPlaylist {
public static final String ENCRYPTION_METHOD_NONE = "NONE";
public static final String ENCRYPTION_METHOD_AES_128 = "AES-128";
public final Uri baseUri;
public final int mediaSequence;
public final int targetDurationSecs;
public final int version;
......@@ -71,7 +70,7 @@ public final class HlsMediaPlaylist {
public HlsMediaPlaylist(Uri baseUri, int mediaSequence, int targetDurationSecs, int version,
boolean live, List<Segment> segments) {
this.baseUri = baseUri;
super(baseUri, HlsPlaylist.TYPE_MEDIA);
this.mediaSequence = mediaSequence;
this.targetDurationSecs = targetDurationSecs;
this.version = version;
......
/*
* Copyright (C) 2014 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.google.android.exoplayer.hls;
import android.net.Uri;
/**
* Represents an HLS playlist.
*/
public abstract class HlsPlaylist {
public final static int TYPE_MASTER = 0;
public final static int TYPE_MEDIA = 1;
public final Uri baseUri;
public final int type;
protected HlsPlaylist(Uri baseUri, int type) {
this.baseUri = baseUri;
this.type = type;
}
}
......@@ -16,6 +16,7 @@
package com.google.android.exoplayer.hls;
import com.google.android.exoplayer.C;
import com.google.android.exoplayer.ParserException;
import com.google.android.exoplayer.hls.HlsMediaPlaylist.Segment;
import com.google.android.exoplayer.util.ManifestParser;
......@@ -27,19 +28,27 @@ import java.io.InputStream;
import java.io.InputStreamReader;
import java.util.ArrayList;
import java.util.Collections;
import java.util.LinkedList;
import java.util.List;
import java.util.Queue;
import java.util.regex.Pattern;
/**
* HLS Media playlists parsing logic.
* HLS playlists parsing logic.
*/
public final class HlsMediaPlaylistParser implements ManifestParser<HlsMediaPlaylist> {
public final class HlsPlaylistParser implements ManifestParser<HlsPlaylist> {
private static final String VERSION_TAG = "#EXT-X-VERSION";
private static final String STREAM_INF_TAG = "#EXT-X-STREAM-INF";
private static final String BANDWIDTH_ATTR = "BANDWIDTH";
private static final String CODECS_ATTR = "CODECS";
private static final String RESOLUTION_ATTR = "RESOLUTION";
private static final String DISCONTINUITY_TAG = "#EXT-X-DISCONTINUITY";
private static final String MEDIA_DURATION_TAG = "#EXTINF";
private static final String MEDIA_SEQUENCE_TAG = "#EXT-X-MEDIA-SEQUENCE";
private static final String TARGET_DURATION_TAG = "#EXT-X-TARGETDURATION";
private static final String VERSION_TAG = "#EXT-X-VERSION";
private static final String ENDLIST_TAG = "#EXT-X-ENDLIST";
private static final String KEY_TAG = "#EXT-X-KEY";
private static final String BYTERANGE_TAG = "#EXT-X-BYTERANGE";
......@@ -48,6 +57,13 @@ public final class HlsMediaPlaylistParser implements ManifestParser<HlsMediaPlay
private static final String URI_ATTR = "URI";
private static final String IV_ATTR = "IV";
private static final Pattern BANDWIDTH_ATTR_REGEX =
Pattern.compile(BANDWIDTH_ATTR + "=(\\d+)\\b");
private static final Pattern CODECS_ATTR_REGEX =
Pattern.compile(CODECS_ATTR + "=\"(.+)\"");
private static final Pattern RESOLUTION_ATTR_REGEX =
Pattern.compile(RESOLUTION_ATTR + "=(\\d+x\\d+)");
private static final Pattern MEDIA_DURATION_REGEX =
Pattern.compile(MEDIA_DURATION_TAG + ":([\\d.]+),");
private static final Pattern MEDIA_SEQUENCE_REGEX =
......@@ -67,16 +83,84 @@ public final class HlsMediaPlaylistParser implements ManifestParser<HlsMediaPlay
Pattern.compile(IV_ATTR + "=([^,.*]+)");
@Override
public HlsMediaPlaylist parse(InputStream inputStream, String inputEncoding,
public HlsPlaylist parse(InputStream inputStream, String inputEncoding,
String contentId, Uri baseUri) throws IOException {
return parseMediaPlaylist(inputStream, inputEncoding, baseUri);
}
private static HlsMediaPlaylist parseMediaPlaylist(InputStream inputStream, String inputEncoding,
Uri baseUri) throws IOException {
BufferedReader reader = new BufferedReader((inputEncoding == null)
? new InputStreamReader(inputStream) : new InputStreamReader(inputStream, inputEncoding));
Queue<String> extraLines = new LinkedList<String>();
String line;
try {
while ((line = reader.readLine()) != null) {
line = line.trim();
if (line.isEmpty()) {
// Do nothing.
} else if (line.startsWith(STREAM_INF_TAG)) {
extraLines.add(line);
return parseMasterPlaylist(new LineIterator(extraLines, reader), baseUri);
} else if (line.startsWith(TARGET_DURATION_TAG)
|| line.startsWith(MEDIA_SEQUENCE_TAG)
|| line.startsWith(MEDIA_DURATION_TAG)
|| line.startsWith(KEY_TAG)
|| line.startsWith(BYTERANGE_TAG)
|| line.equals(DISCONTINUITY_TAG)
|| line.equals(ENDLIST_TAG)) {
extraLines.add(line);
return parseMediaPlaylist(new LineIterator(extraLines, reader), baseUri);
} else if (line.startsWith(VERSION_TAG)) {
extraLines.add(line);
} else if (!line.startsWith("#")) {
throw new ParserException("Missing a tag before URL.");
}
}
} finally {
reader.close();
}
throw new ParserException("Failed to parse the playlist, could not identify any tags.");
}
private static HlsMasterPlaylist parseMasterPlaylist(LineIterator iterator, Uri baseUri)
throws IOException {
List<Variant> variants = new ArrayList<Variant>();
int bandwidth = 0;
String[] codecs = null;
int width = -1;
int height = -1;
int variantIndex = 0;
String line;
while (iterator.hasNext()) {
line = iterator.next();
if (line.startsWith(STREAM_INF_TAG)) {
bandwidth = HlsParserUtil.parseIntAttr(line, BANDWIDTH_ATTR_REGEX, BANDWIDTH_ATTR);
String codecsString = HlsParserUtil.parseOptionalStringAttr(line, CODECS_ATTR_REGEX);
if (codecsString != null) {
codecs = codecsString.split("(\\s*,\\s*)|(\\s*$)");
} else {
codecs = null;
}
String resolutionString = HlsParserUtil.parseOptionalStringAttr(line,
RESOLUTION_ATTR_REGEX);
if (resolutionString != null) {
String[] widthAndHeight = resolutionString.split("x");
width = Integer.parseInt(widthAndHeight[0]);
height = Integer.parseInt(widthAndHeight[1]);
} else {
width = -1;
height = -1;
}
} else if (!line.startsWith("#")) {
variants.add(new Variant(variantIndex++, line, bandwidth, codecs, width, height));
bandwidth = 0;
codecs = null;
width = -1;
height = -1;
}
}
return new HlsMasterPlaylist(baseUri, Collections.unmodifiableList(variants));
}
private static HlsMediaPlaylist parseMediaPlaylist(LineIterator iterator, Uri baseUri)
throws IOException {
int mediaSequence = 0;
int targetDurationSecs = 0;
int version = 1; // Default version == 1.
......@@ -95,11 +179,8 @@ public final class HlsMediaPlaylistParser implements ManifestParser<HlsMediaPlay
int segmentMediaSequence = 0;
String line;
while ((line = reader.readLine()) != null) {
line = line.trim();
if (line.isEmpty()) {
continue;
}
while (iterator.hasNext()) {
line = iterator.next();
if (line.startsWith(TARGET_DURATION_TAG)) {
targetDurationSecs = HlsParserUtil.parseIntAttr(line, TARGET_DURATION_REGEX,
TARGET_DURATION_TAG);
......@@ -158,4 +239,44 @@ public final class HlsMediaPlaylistParser implements ManifestParser<HlsMediaPlay
Collections.unmodifiableList(segments));
}
private static class LineIterator {
private final BufferedReader reader;
private final Queue<String> extraLines;
private String next;
public LineIterator(Queue<String> extraLines, BufferedReader reader) {
this.extraLines = extraLines;
this.reader = reader;
}
public boolean hasNext() throws IOException {
if (next != null) {
return true;
}
if (!extraLines.isEmpty()) {
next = extraLines.poll();
return true;
}
while ((next = reader.readLine()) != null) {
next = next.trim();
if (!next.isEmpty()) {
return true;
}
}
return false;
}
public String next() throws IOException {
String result = null;
if (hasNext()) {
result = next;
next = null;
}
return result;
}
}
}
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