Simplify NetworkLoader/Parser/ManifestParser

The only downside of this change is that MediaPresentationDescriptionParser
is no longer stateless.
This commit is contained in:
Oliver Woodman 2015-02-25 12:09:06 +00:00
parent 0fdcb3347c
commit d2da3bbf8a
12 changed files with 151 additions and 199 deletions

View File

@ -206,13 +206,13 @@ public class PlayerActivity extends Activity implements SurfaceHolder.Callback,
String userAgent = DemoUtil.getUserAgent(this); String userAgent = DemoUtil.getUserAgent(this);
switch (contentType) { switch (contentType) {
case DemoUtil.TYPE_SS: case DemoUtil.TYPE_SS:
return new SmoothStreamingRendererBuilder(userAgent, contentUri.toString(), contentId, return new SmoothStreamingRendererBuilder(userAgent, contentUri.toString(),
new SmoothStreamingTestMediaDrmCallback(), debugTextView); new SmoothStreamingTestMediaDrmCallback(), debugTextView);
case DemoUtil.TYPE_DASH: case DemoUtil.TYPE_DASH:
return new DashRendererBuilder(userAgent, contentUri.toString(), contentId, return new DashRendererBuilder(userAgent, contentUri.toString(),
new WidevineTestMediaDrmCallback(contentId), debugTextView, audioCapabilities); new WidevineTestMediaDrmCallback(contentId), debugTextView, audioCapabilities);
case DemoUtil.TYPE_HLS: case DemoUtil.TYPE_HLS:
return new HlsRendererBuilder(userAgent, contentUri.toString(), contentId); return new HlsRendererBuilder(userAgent, contentUri.toString());
default: default:
return new DefaultRendererBuilder(this, contentUri, debugTextView); return new DefaultRendererBuilder(this, contentUri, debugTextView);
} }

View File

@ -93,7 +93,6 @@ public class DashRendererBuilder implements RendererBuilder,
private final String userAgent; private final String userAgent;
private final String url; private final String url;
private final String contentId;
private final MediaDrmCallback drmCallback; private final MediaDrmCallback drmCallback;
private final TextView debugTextView; private final TextView debugTextView;
private final AudioCapabilities audioCapabilities; private final AudioCapabilities audioCapabilities;
@ -105,11 +104,10 @@ public class DashRendererBuilder implements RendererBuilder,
private MediaPresentationDescription manifest; private MediaPresentationDescription manifest;
private long elapsedRealtimeOffset; private long elapsedRealtimeOffset;
public DashRendererBuilder(String userAgent, String url, String contentId, public DashRendererBuilder(String userAgent, String url, MediaDrmCallback drmCallback,
MediaDrmCallback drmCallback, TextView debugTextView, AudioCapabilities audioCapabilities) { TextView debugTextView, AudioCapabilities audioCapabilities) {
this.userAgent = userAgent; this.userAgent = userAgent;
this.url = url; this.url = url;
this.contentId = contentId;
this.drmCallback = drmCallback; this.drmCallback = drmCallback;
this.debugTextView = debugTextView; this.debugTextView = debugTextView;
this.audioCapabilities = audioCapabilities; this.audioCapabilities = audioCapabilities;
@ -120,13 +118,12 @@ public class DashRendererBuilder implements RendererBuilder,
this.player = player; this.player = player;
this.callback = callback; this.callback = callback;
MediaPresentationDescriptionParser parser = new MediaPresentationDescriptionParser(); MediaPresentationDescriptionParser parser = new MediaPresentationDescriptionParser();
manifestFetcher = new ManifestFetcher<MediaPresentationDescription>(parser, contentId, url, manifestFetcher = new ManifestFetcher<MediaPresentationDescription>(url, userAgent, parser);
userAgent);
manifestFetcher.singleLoad(player.getMainHandler().getLooper(), this); manifestFetcher.singleLoad(player.getMainHandler().getLooper(), this);
} }
@Override @Override
public void onManifest(String contentId, MediaPresentationDescription manifest) { public void onSingleManifest(MediaPresentationDescription manifest) {
this.manifest = manifest; this.manifest = manifest;
if (manifest.dynamic && manifest.utcTiming != null) { if (manifest.dynamic && manifest.utcTiming != null) {
UtcTimingElementResolver.resolveTimingElement(userAgent, manifest.utcTiming, UtcTimingElementResolver.resolveTimingElement(userAgent, manifest.utcTiming,
@ -137,7 +134,7 @@ public class DashRendererBuilder implements RendererBuilder,
} }
@Override @Override
public void onManifestError(String contentId, IOException e) { public void onSingleManifestError(IOException e) {
callback.onRenderersError(e); callback.onRenderersError(e);
} }

View File

@ -45,15 +45,13 @@ public class HlsRendererBuilder implements RendererBuilder, ManifestCallback<Hls
private final String userAgent; private final String userAgent;
private final String url; private final String url;
private final String contentId;
private DemoPlayer player; private DemoPlayer player;
private RendererBuilderCallback callback; private RendererBuilderCallback callback;
public HlsRendererBuilder(String userAgent, String url, String contentId) { public HlsRendererBuilder(String userAgent, String url) {
this.userAgent = userAgent; this.userAgent = userAgent;
this.url = url; this.url = url;
this.contentId = contentId;
} }
@Override @Override
@ -62,17 +60,17 @@ public class HlsRendererBuilder implements RendererBuilder, ManifestCallback<Hls
this.callback = callback; this.callback = callback;
HlsPlaylistParser parser = new HlsPlaylistParser(); HlsPlaylistParser parser = new HlsPlaylistParser();
ManifestFetcher<HlsPlaylist> playlistFetcher = ManifestFetcher<HlsPlaylist> playlistFetcher =
new ManifestFetcher<HlsPlaylist>(parser, contentId, url, userAgent); new ManifestFetcher<HlsPlaylist>(url, userAgent, parser);
playlistFetcher.singleLoad(player.getMainHandler().getLooper(), this); playlistFetcher.singleLoad(player.getMainHandler().getLooper(), this);
} }
@Override @Override
public void onManifestError(String contentId, IOException e) { public void onSingleManifestError(IOException e) {
callback.onRenderersError(e); callback.onRenderersError(e);
} }
@Override @Override
public void onManifest(String contentId, HlsPlaylist manifest) { public void onSingleManifest(HlsPlaylist manifest) {
DefaultBandwidthMeter bandwidthMeter = new DefaultBandwidthMeter(); DefaultBandwidthMeter bandwidthMeter = new DefaultBandwidthMeter();
DataSource dataSource = new UriDataSource(userAgent, bandwidthMeter); DataSource dataSource = new UriDataSource(userAgent, bandwidthMeter);

View File

@ -70,7 +70,6 @@ public class SmoothStreamingRendererBuilder implements RendererBuilder,
private final String userAgent; private final String userAgent;
private final String url; private final String url;
private final String contentId;
private final MediaDrmCallback drmCallback; private final MediaDrmCallback drmCallback;
private final TextView debugTextView; private final TextView debugTextView;
@ -78,11 +77,10 @@ public class SmoothStreamingRendererBuilder implements RendererBuilder,
private RendererBuilderCallback callback; private RendererBuilderCallback callback;
private ManifestFetcher<SmoothStreamingManifest> manifestFetcher; private ManifestFetcher<SmoothStreamingManifest> manifestFetcher;
public SmoothStreamingRendererBuilder(String userAgent, String url, String contentId, public SmoothStreamingRendererBuilder(String userAgent, String url, MediaDrmCallback drmCallback,
MediaDrmCallback drmCallback, TextView debugTextView) { TextView debugTextView) {
this.userAgent = userAgent; this.userAgent = userAgent;
this.url = url; this.url = url;
this.contentId = contentId;
this.drmCallback = drmCallback; this.drmCallback = drmCallback;
this.debugTextView = debugTextView; this.debugTextView = debugTextView;
} }
@ -92,18 +90,18 @@ public class SmoothStreamingRendererBuilder implements RendererBuilder,
this.player = player; this.player = player;
this.callback = callback; this.callback = callback;
SmoothStreamingManifestParser parser = new SmoothStreamingManifestParser(); SmoothStreamingManifestParser parser = new SmoothStreamingManifestParser();
manifestFetcher = new ManifestFetcher<SmoothStreamingManifest>(parser, contentId, manifestFetcher = new ManifestFetcher<SmoothStreamingManifest>(url + "/Manifest", userAgent,
url + "/Manifest", userAgent); parser);
manifestFetcher.singleLoad(player.getMainHandler().getLooper(), this); manifestFetcher.singleLoad(player.getMainHandler().getLooper(), this);
} }
@Override @Override
public void onManifestError(String contentId, IOException exception) { public void onSingleManifestError(IOException exception) {
callback.onRenderersError(exception); callback.onRenderersError(exception);
} }
@Override @Override
public void onManifest(String contentId, SmoothStreamingManifest manifest) { public void onSingleManifest(SmoothStreamingManifest manifest) {
Handler mainHandler = player.getMainHandler(); Handler mainHandler = player.getMainHandler();
LoadControl loadControl = new DefaultLoadControl(new BufferPool(BUFFER_SEGMENT_SIZE)); LoadControl loadControl = new DefaultLoadControl(new BufferPool(BUFFER_SEGMENT_SIZE));
DefaultBandwidthMeter bandwidthMeter = new DefaultBandwidthMeter(mainHandler, player); DefaultBandwidthMeter bandwidthMeter = new DefaultBandwidthMeter(mainHandler, player);

View File

@ -22,8 +22,8 @@ import com.google.android.exoplayer.dash.mpd.SegmentBase.SegmentTemplate;
import com.google.android.exoplayer.dash.mpd.SegmentBase.SegmentTimelineElement; import com.google.android.exoplayer.dash.mpd.SegmentBase.SegmentTimelineElement;
import com.google.android.exoplayer.dash.mpd.SegmentBase.SingleSegmentBase; import com.google.android.exoplayer.dash.mpd.SegmentBase.SingleSegmentBase;
import com.google.android.exoplayer.util.Assertions; import com.google.android.exoplayer.util.Assertions;
import com.google.android.exoplayer.util.ManifestParser;
import com.google.android.exoplayer.util.MimeTypes; import com.google.android.exoplayer.util.MimeTypes;
import com.google.android.exoplayer.util.NetworkLoadable;
import com.google.android.exoplayer.util.Util; import com.google.android.exoplayer.util.Util;
import android.net.Uri; import android.net.Uri;
@ -44,11 +44,25 @@ import java.util.List;
* A parser of media presentation description files. * A parser of media presentation description files.
*/ */
public class MediaPresentationDescriptionParser extends DefaultHandler public class MediaPresentationDescriptionParser extends DefaultHandler
implements ManifestParser<MediaPresentationDescription> { implements NetworkLoadable.Parser<MediaPresentationDescription> {
private final String contentId;
private final XmlPullParserFactory xmlParserFactory; private final XmlPullParserFactory xmlParserFactory;
/**
* Equivalent to calling {@code new MediaPresentationDescriptionParser(null)}.
*/
public MediaPresentationDescriptionParser() { public MediaPresentationDescriptionParser() {
this(null);
}
/**
* @param contentId An optional content identifier to include in the parsed manifest.
*/
// TODO: Remove the need to inject a content identifier here, by not including it in the parsed
// manifest. Instead, it should be injected directly where needed (i.e. DashChunkSource).
public MediaPresentationDescriptionParser(String contentId) {
this.contentId = contentId;
try { try {
xmlParserFactory = XmlPullParserFactory.newInstance(); xmlParserFactory = XmlPullParserFactory.newInstance();
} catch (XmlPullParserException e) { } catch (XmlPullParserException e) {
@ -59,8 +73,8 @@ public class MediaPresentationDescriptionParser extends DefaultHandler
// MPD parsing. // MPD parsing.
@Override @Override
public MediaPresentationDescription parse(InputStream inputStream, String inputEncoding, public MediaPresentationDescription parse(String connectionUrl, InputStream inputStream,
String contentId, Uri baseUrl) throws IOException, ParserException { String inputEncoding) throws IOException, ParserException {
try { try {
XmlPullParser xpp = xmlParserFactory.newPullParser(); XmlPullParser xpp = xmlParserFactory.newPullParser();
xpp.setInput(inputStream, inputEncoding); xpp.setInput(inputStream, inputEncoding);
@ -69,7 +83,7 @@ public class MediaPresentationDescriptionParser extends DefaultHandler
throw new ParserException( throw new ParserException(
"inputStream does not contain a valid media presentation description"); "inputStream does not contain a valid media presentation description");
} }
return parseMediaPresentationDescription(xpp, contentId, baseUrl); return parseMediaPresentationDescription(xpp, Util.parseBaseUri(connectionUrl));
} catch (XmlPullParserException e) { } catch (XmlPullParserException e) {
throw new ParserException(e); throw new ParserException(e);
} catch (ParseException e) { } catch (ParseException e) {
@ -78,7 +92,7 @@ public class MediaPresentationDescriptionParser extends DefaultHandler
} }
protected MediaPresentationDescription parseMediaPresentationDescription(XmlPullParser xpp, protected MediaPresentationDescription parseMediaPresentationDescription(XmlPullParser xpp,
String contentId, Uri baseUrl) throws XmlPullParserException, IOException, ParseException { Uri baseUrl) throws XmlPullParserException, IOException, ParseException {
long availabilityStartTime = parseDateTime(xpp, "availabilityStartTime", -1); long availabilityStartTime = parseDateTime(xpp, "availabilityStartTime", -1);
long durationMs = parseDuration(xpp, "mediaPresentationDuration", -1); long durationMs = parseDuration(xpp, "mediaPresentationDuration", -1);
long minBufferTimeMs = parseDuration(xpp, "minBufferTime", -1); long minBufferTimeMs = parseDuration(xpp, "minBufferTime", -1);
@ -97,7 +111,7 @@ public class MediaPresentationDescriptionParser extends DefaultHandler
} else if (isStartTag(xpp, "UTCTiming")) { } else if (isStartTag(xpp, "UTCTiming")) {
utcTiming = parseUtcTiming(xpp); utcTiming = parseUtcTiming(xpp);
} else if (isStartTag(xpp, "Period")) { } else if (isStartTag(xpp, "Period")) {
periods.add(parsePeriod(xpp, contentId, baseUrl, durationMs)); periods.add(parsePeriod(xpp, baseUrl, durationMs));
} }
} while (!isEndTag(xpp, "MPD")); } while (!isEndTag(xpp, "MPD"));
@ -123,7 +137,7 @@ public class MediaPresentationDescriptionParser extends DefaultHandler
return new UtcTimingElement(schemeIdUri, value); return new UtcTimingElement(schemeIdUri, value);
} }
protected Period parsePeriod(XmlPullParser xpp, String contentId, Uri baseUrl, long mpdDurationMs) protected Period parsePeriod(XmlPullParser xpp, Uri baseUrl, long mpdDurationMs)
throws XmlPullParserException, IOException { throws XmlPullParserException, IOException {
String id = xpp.getAttributeValue(null, "id"); String id = xpp.getAttributeValue(null, "id");
long startMs = parseDuration(xpp, "start", 0); long startMs = parseDuration(xpp, "start", 0);
@ -135,7 +149,7 @@ public class MediaPresentationDescriptionParser extends DefaultHandler
if (isStartTag(xpp, "BaseURL")) { if (isStartTag(xpp, "BaseURL")) {
baseUrl = parseBaseUrl(xpp, baseUrl); baseUrl = parseBaseUrl(xpp, baseUrl);
} else if (isStartTag(xpp, "AdaptationSet")) { } else if (isStartTag(xpp, "AdaptationSet")) {
adaptationSets.add(parseAdaptationSet(xpp, contentId, baseUrl, startMs, durationMs, adaptationSets.add(parseAdaptationSet(xpp, baseUrl, startMs, durationMs,
segmentBase)); segmentBase));
} else if (isStartTag(xpp, "SegmentBase")) { } else if (isStartTag(xpp, "SegmentBase")) {
segmentBase = parseSegmentBase(xpp, baseUrl, null); segmentBase = parseSegmentBase(xpp, baseUrl, null);
@ -156,9 +170,8 @@ public class MediaPresentationDescriptionParser extends DefaultHandler
// AdaptationSet parsing. // AdaptationSet parsing.
protected AdaptationSet parseAdaptationSet(XmlPullParser xpp, String contentId, Uri baseUrl, protected AdaptationSet parseAdaptationSet(XmlPullParser xpp, Uri baseUrl, long periodStartMs,
long periodStartMs, long periodDurationMs, SegmentBase segmentBase) long periodDurationMs, SegmentBase segmentBase) throws XmlPullParserException, IOException {
throws XmlPullParserException, IOException {
String mimeType = xpp.getAttributeValue(null, "mimeType"); String mimeType = xpp.getAttributeValue(null, "mimeType");
String language = xpp.getAttributeValue(null, "lang"); String language = xpp.getAttributeValue(null, "lang");
@ -181,7 +194,7 @@ public class MediaPresentationDescriptionParser extends DefaultHandler
contentType = checkAdaptationSetTypeConsistency(contentType, contentType = checkAdaptationSetTypeConsistency(contentType,
parseAdaptationSetType(xpp.getAttributeValue(null, "contentType"))); parseAdaptationSetType(xpp.getAttributeValue(null, "contentType")));
} else if (isStartTag(xpp, "Representation")) { } else if (isStartTag(xpp, "Representation")) {
Representation representation = parseRepresentation(xpp, contentId, baseUrl, periodStartMs, Representation representation = parseRepresentation(xpp, baseUrl, periodStartMs,
periodDurationMs, mimeType, language, segmentBase); periodDurationMs, mimeType, language, segmentBase);
contentType = checkAdaptationSetTypeConsistency(contentType, contentType = checkAdaptationSetTypeConsistency(contentType,
parseAdaptationSetTypeFromMimeType(representation.format.mimeType)); parseAdaptationSetTypeFromMimeType(representation.format.mimeType));
@ -274,9 +287,9 @@ public class MediaPresentationDescriptionParser extends DefaultHandler
// Representation parsing. // Representation parsing.
protected Representation parseRepresentation(XmlPullParser xpp, String contentId, Uri baseUrl, protected Representation parseRepresentation(XmlPullParser xpp, Uri baseUrl, long periodStartMs,
long periodStartMs, long periodDurationMs, String mimeType, String language, long periodDurationMs, String mimeType, String language, SegmentBase segmentBase)
SegmentBase segmentBase) throws XmlPullParserException, IOException { throws XmlPullParserException, IOException {
String id = xpp.getAttributeValue(null, "id"); String id = xpp.getAttributeValue(null, "id");
int bandwidth = parseInt(xpp, "bandwidth"); int bandwidth = parseInt(xpp, "bandwidth");
int audioSamplingRate = parseInt(xpp, "audioSamplingRate"); int audioSamplingRate = parseInt(xpp, "audioSamplingRate");

View File

@ -62,16 +62,13 @@ public class UtcTimingElementResolver implements Loader.Callback {
void onTimestampError(UtcTimingElement utcTiming, IOException e); void onTimestampError(UtcTimingElement utcTiming, IOException e);
} }
private static final int TYPE_XS = 0;
private static final int TYPE_ISO = 1;
private final String userAgent; private final String userAgent;
private final UtcTimingElement timingElement; private final UtcTimingElement timingElement;
private final long timingElementElapsedRealtime; private final long timingElementElapsedRealtime;
private final UtcTimingCallback callback; private final UtcTimingCallback callback;
private Loader singleUseLoader; private Loader singleUseLoader;
private HttpTimestampLoadable singleUseLoadable; private NetworkLoadable<Long> singleUseLoadable;
/** /**
* Resolves a {@link UtcTimingElement}. * Resolves a {@link UtcTimingElement}.
@ -102,10 +99,10 @@ public class UtcTimingElementResolver implements Loader.Callback {
if (Util.areEqual(scheme, "urn:mpeg:dash:utc:direct:2012")) { if (Util.areEqual(scheme, "urn:mpeg:dash:utc:direct:2012")) {
resolveDirect(); resolveDirect();
} else if (Util.areEqual(scheme, "urn:mpeg:dash:utc:http-iso:2014")) { } else if (Util.areEqual(scheme, "urn:mpeg:dash:utc:http-iso:2014")) {
resolveHttp(TYPE_ISO); resolveHttp(new Iso8601Parser());
} else if (Util.areEqual(scheme, "urn:mpeg:dash:utc:http-xsdate:2012") } else if (Util.areEqual(scheme, "urn:mpeg:dash:utc:http-xsdate:2012")
|| Util.areEqual(scheme, "urn:mpeg:dash:utc:http-xsdate:2014")) { || Util.areEqual(scheme, "urn:mpeg:dash:utc:http-xsdate:2014")) {
resolveHttp(TYPE_XS); resolveHttp(new XsDateTimeParser());
} else { } else {
// Unsupported scheme. // Unsupported scheme.
callback.onTimestampError(timingElement, new IOException("Unsupported utc timing scheme")); callback.onTimestampError(timingElement, new IOException("Unsupported utc timing scheme"));
@ -122,9 +119,9 @@ public class UtcTimingElementResolver implements Loader.Callback {
} }
} }
private void resolveHttp(int type) { private void resolveHttp(NetworkLoadable.Parser<Long> parser) {
singleUseLoader = new Loader("utctiming"); singleUseLoader = new Loader("utctiming");
singleUseLoadable = new HttpTimestampLoadable(timingElement.value, userAgent, type); singleUseLoadable = new NetworkLoadable<Long>(timingElement.value, userAgent, parser);
singleUseLoader.startLoading(singleUseLoadable, this); singleUseLoader.startLoading(singleUseLoadable, this);
} }
@ -150,32 +147,31 @@ public class UtcTimingElementResolver implements Loader.Callback {
singleUseLoader.release(); singleUseLoader.release();
} }
private static class HttpTimestampLoadable extends NetworkLoadable<Long> { private static class XsDateTimeParser implements NetworkLoadable.Parser<Long> {
private final int type;
public HttpTimestampLoadable(String url, String userAgent, int type) {
super(url, userAgent);
this.type = type;
}
@Override @Override
protected Long parse(String connectionUrl, InputStream inputStream, String inputEncoding) public Long parse(String connectionUrl, InputStream inputStream, String inputEncoding)
throws ParserException, IOException { throws ParserException, IOException {
BufferedReader reader = new BufferedReader(new InputStreamReader(inputStream)); String firstLine = new BufferedReader(new InputStreamReader(inputStream)).readLine();
String firstLine = reader.readLine();
try { try {
switch (type) { return Util.parseXsDateTime(firstLine);
case TYPE_XS: } catch (ParseException e) {
return Util.parseXsDateTime(firstLine); throw new ParserException(e);
case TYPE_ISO: }
// TODO: It may be necessary to handle timestamp offsets from UTC. }
SimpleDateFormat format = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss'Z'", Locale.US);
return format.parse(firstLine).getTime(); }
default:
// Never happens. private static class Iso8601Parser implements NetworkLoadable.Parser<Long> {
throw new RuntimeException();
} @Override
public Long parse(String connectionUrl, InputStream inputStream, String inputEncoding)
throws ParserException, IOException {
String firstLine = new BufferedReader(new InputStreamReader(inputStream)).readLine();
try {
// TODO: It may be necessary to handle timestamp offsets from UTC.
SimpleDateFormat format = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss'Z'", Locale.US);
return format.parse(firstLine).getTime();
} catch (ParseException e) { } catch (ParseException e) {
throw new ParserException(e); throw new ParserException(e);
} }

View File

@ -433,8 +433,8 @@ public class HlsChunkSource {
private MediaPlaylistChunk newMediaPlaylistChunk(int variantIndex) { private MediaPlaylistChunk newMediaPlaylistChunk(int variantIndex) {
Uri mediaPlaylistUri = Util.getMergedUri(baseUri, enabledVariants[variantIndex].url); Uri mediaPlaylistUri = Util.getMergedUri(baseUri, enabledVariants[variantIndex].url);
DataSpec dataSpec = new DataSpec(mediaPlaylistUri, 0, C.LENGTH_UNBOUNDED, null); DataSpec dataSpec = new DataSpec(mediaPlaylistUri, 0, C.LENGTH_UNBOUNDED, null);
Uri baseUri = Util.parseBaseUri(mediaPlaylistUri.toString()); return new MediaPlaylistChunk(variantIndex, upstreamDataSource, dataSpec,
return new MediaPlaylistChunk(variantIndex, upstreamDataSource, dataSpec, baseUri); mediaPlaylistUri.toString());
} }
private EncryptionKeyChunk newEncryptionKeyChunk(Uri keyUri, String iv) { private EncryptionKeyChunk newEncryptionKeyChunk(Uri keyUri, String iv) {
@ -546,19 +546,19 @@ public class HlsChunkSource {
@SuppressWarnings("hiding") @SuppressWarnings("hiding")
/* package */ final int variantIndex; /* package */ final int variantIndex;
private final Uri playlistBaseUri; private final String playlistUrl;
public MediaPlaylistChunk(int variantIndex, DataSource dataSource, DataSpec dataSpec, public MediaPlaylistChunk(int variantIndex, DataSource dataSource, DataSpec dataSpec,
Uri playlistBaseUri) { String playlistUrl) {
super(dataSource, dataSpec, scratchSpace); super(dataSource, dataSpec, scratchSpace);
this.variantIndex = variantIndex; this.variantIndex = variantIndex;
this.playlistBaseUri = playlistBaseUri; this.playlistUrl = playlistUrl;
} }
@Override @Override
protected void consume(byte[] data, int limit) throws IOException { protected void consume(byte[] data, int limit) throws IOException {
HlsPlaylist playlist = playlistParser.parse(new ByteArrayInputStream(data, 0, limit), HlsPlaylist playlist = playlistParser.parse(playlistUrl,
null, null, playlistBaseUri); new ByteArrayInputStream(data, 0, limit), null);
Assertions.checkState(playlist.type == HlsPlaylist.TYPE_MEDIA); Assertions.checkState(playlist.type == HlsPlaylist.TYPE_MEDIA);
HlsMediaPlaylist mediaPlaylist = (HlsMediaPlaylist) playlist; HlsMediaPlaylist mediaPlaylist = (HlsMediaPlaylist) playlist;
setMediaPlaylist(variantIndex, mediaPlaylist); setMediaPlaylist(variantIndex, mediaPlaylist);

View File

@ -18,7 +18,8 @@ package com.google.android.exoplayer.hls;
import com.google.android.exoplayer.C; import com.google.android.exoplayer.C;
import com.google.android.exoplayer.ParserException; import com.google.android.exoplayer.ParserException;
import com.google.android.exoplayer.hls.HlsMediaPlaylist.Segment; import com.google.android.exoplayer.hls.HlsMediaPlaylist.Segment;
import com.google.android.exoplayer.util.ManifestParser; import com.google.android.exoplayer.util.NetworkLoadable;
import com.google.android.exoplayer.util.Util;
import android.net.Uri; import android.net.Uri;
@ -36,7 +37,7 @@ import java.util.regex.Pattern;
/** /**
* HLS playlists parsing logic. * HLS playlists parsing logic.
*/ */
public final class HlsPlaylistParser implements ManifestParser<HlsPlaylist> { public final class HlsPlaylistParser implements NetworkLoadable.Parser<HlsPlaylist> {
private static final String VERSION_TAG = "#EXT-X-VERSION"; private static final String VERSION_TAG = "#EXT-X-VERSION";
@ -83,8 +84,9 @@ public final class HlsPlaylistParser implements ManifestParser<HlsPlaylist> {
Pattern.compile(IV_ATTR + "=([^,.*]+)"); Pattern.compile(IV_ATTR + "=([^,.*]+)");
@Override @Override
public HlsPlaylist parse(InputStream inputStream, String inputEncoding, public HlsPlaylist parse(String connectionUrl, InputStream inputStream, String inputEncoding)
String contentId, Uri baseUri) throws IOException { throws IOException, ParserException {
Uri baseUri = Util.parseBaseUri(connectionUrl);
BufferedReader reader = new BufferedReader((inputEncoding == null) BufferedReader reader = new BufferedReader((inputEncoding == null)
? new InputStreamReader(inputStream) : new InputStreamReader(inputStream, inputEncoding)); ? new InputStreamReader(inputStream) : new InputStreamReader(inputStream, inputEncoding));
Queue<String> extraLines = new LinkedList<String>(); Queue<String> extraLines = new LinkedList<String>();

View File

@ -21,8 +21,9 @@ import com.google.android.exoplayer.smoothstreaming.SmoothStreamingManifest.Stre
import com.google.android.exoplayer.smoothstreaming.SmoothStreamingManifest.TrackElement; import com.google.android.exoplayer.smoothstreaming.SmoothStreamingManifest.TrackElement;
import com.google.android.exoplayer.util.Assertions; import com.google.android.exoplayer.util.Assertions;
import com.google.android.exoplayer.util.CodecSpecificDataUtil; import com.google.android.exoplayer.util.CodecSpecificDataUtil;
import com.google.android.exoplayer.util.ManifestParser;
import com.google.android.exoplayer.util.MimeTypes; import com.google.android.exoplayer.util.MimeTypes;
import com.google.android.exoplayer.util.NetworkLoadable;
import com.google.android.exoplayer.util.Util;
import android.net.Uri; import android.net.Uri;
import android.util.Base64; import android.util.Base64;
@ -45,7 +46,8 @@ import java.util.UUID;
* @see <a href="http://msdn.microsoft.com/en-us/library/ee673436(v=vs.90).aspx"> * @see <a href="http://msdn.microsoft.com/en-us/library/ee673436(v=vs.90).aspx">
* IIS Smooth Streaming Client Manifest Format</a> * IIS Smooth Streaming Client Manifest Format</a>
*/ */
public class SmoothStreamingManifestParser implements ManifestParser<SmoothStreamingManifest> { public class SmoothStreamingManifestParser implements
NetworkLoadable.Parser<SmoothStreamingManifest> {
private final XmlPullParserFactory xmlParserFactory; private final XmlPullParserFactory xmlParserFactory;
@ -58,12 +60,13 @@ public class SmoothStreamingManifestParser implements ManifestParser<SmoothStrea
} }
@Override @Override
public SmoothStreamingManifest parse(InputStream inputStream, String inputEncoding, public SmoothStreamingManifest parse(String connectionUrl, InputStream inputStream,
String contentId, Uri baseUri) throws IOException, ParserException { String inputEncoding) throws IOException, ParserException {
try { try {
XmlPullParser xmlParser = xmlParserFactory.newPullParser(); XmlPullParser xmlParser = xmlParserFactory.newPullParser();
xmlParser.setInput(inputStream, inputEncoding); xmlParser.setInput(inputStream, inputEncoding);
SmoothStreamMediaParser smoothStreamMediaParser = new SmoothStreamMediaParser(null, baseUri); SmoothStreamMediaParser smoothStreamMediaParser = new SmoothStreamMediaParser(null,
Util.parseBaseUri(connectionUrl));
return (SmoothStreamingManifest) smoothStreamMediaParser.parse(xmlParser); return (SmoothStreamingManifest) smoothStreamMediaParser.parse(xmlParser);
} catch (XmlPullParserException e) { } catch (XmlPullParserException e) {
throw new ParserException(e); throw new ParserException(e);

View File

@ -15,7 +15,6 @@
*/ */
package com.google.android.exoplayer.util; package com.google.android.exoplayer.util;
import com.google.android.exoplayer.ParserException;
import com.google.android.exoplayer.upstream.Loader; import com.google.android.exoplayer.upstream.Loader;
import com.google.android.exoplayer.upstream.Loader.Loadable; import com.google.android.exoplayer.upstream.Loader.Loadable;
@ -25,7 +24,6 @@ import android.os.SystemClock;
import android.util.Pair; import android.util.Pair;
import java.io.IOException; import java.io.IOException;
import java.io.InputStream;
import java.util.concurrent.CancellationException; import java.util.concurrent.CancellationException;
/** /**
@ -58,24 +56,21 @@ public class ManifestFetcher<T> implements Loader.Callback {
/** /**
* Invoked when the load has successfully completed. * Invoked when the load has successfully completed.
* *
* @param contentId The content id of the media.
* @param manifest The loaded manifest. * @param manifest The loaded manifest.
*/ */
void onManifest(String contentId, T manifest); void onSingleManifest(T manifest);
/** /**
* Invoked when the load has failed. * Invoked when the load has failed.
* *
* @param contentId The content id of the media.
* @param e The cause of the failure. * @param e The cause of the failure.
*/ */
void onManifestError(String contentId, IOException e); void onSingleManifestError(IOException e);
} }
/* package */ final ManifestParser<T> parser; private final NetworkLoadable.Parser<T> parser;
/* package */ final String contentId; private final String userAgent;
/* package */ final String userAgent;
private final Handler eventHandler; private final Handler eventHandler;
private final EventListener eventListener; private final EventListener eventListener;
@ -83,7 +78,7 @@ public class ManifestFetcher<T> implements Loader.Callback {
private int enabledCount; private int enabledCount;
private Loader loader; private Loader loader;
private ManifestLoadable<T> currentLoadable; private NetworkLoadable<T> currentLoadable;
private int loadExceptionCount; private int loadExceptionCount;
private long loadExceptionTimestamp; private long loadExceptionTimestamp;
@ -92,21 +87,26 @@ public class ManifestFetcher<T> implements Loader.Callback {
private volatile T manifest; private volatile T manifest;
private volatile long manifestLoadTimestamp; private volatile long manifestLoadTimestamp;
public ManifestFetcher(ManifestParser<T> parser, String contentId, String manifestUrl, /**
String userAgent) { * @param manifestUrl The manifest location.
this(parser, contentId, manifestUrl, userAgent, null, null); * @param userAgent The User-Agent string that should be used.
* @param parser A parser to parse the loaded manifest data.
*/
public ManifestFetcher(String manifestUrl, String userAgent, NetworkLoadable.Parser<T> parser) {
this(manifestUrl, userAgent, parser, null, null);
} }
/** /**
* @param parser A parser to parse the loaded manifest data.
* @param contentId The content id of the content being loaded. May be null.
* @param manifestUrl The manifest location. * @param manifestUrl The manifest location.
* @param userAgent The User-Agent string that should be used. * @param userAgent The User-Agent string that should be used.
* @param parser A parser to parse the loaded manifest data.
* @param eventHandler A handler to use when delivering events to {@code eventListener}. May be
* null if delivery of events is not required.
* @param eventListener A listener of events. May be null if delivery of events is not required.
*/ */
public ManifestFetcher(ManifestParser<T> parser, String contentId, String manifestUrl, public ManifestFetcher(String manifestUrl, String userAgent, NetworkLoadable.Parser<T> parser,
String userAgent, Handler eventHandler, EventListener eventListener) { Handler eventHandler, EventListener eventListener) {
this.parser = parser; this.parser = parser;
this.contentId = contentId;
this.manifestUrl = manifestUrl; this.manifestUrl = manifestUrl;
this.userAgent = userAgent; this.userAgent = userAgent;
this.eventHandler = eventHandler; this.eventHandler = eventHandler;
@ -130,7 +130,8 @@ public class ManifestFetcher<T> implements Loader.Callback {
* @param callback The callback to receive the result. * @param callback The callback to receive the result.
*/ */
public void singleLoad(Looper callbackLooper, final ManifestCallback<T> callback) { public void singleLoad(Looper callbackLooper, final ManifestCallback<T> callback) {
SingleFetchHelper fetchHelper = new SingleFetchHelper(callbackLooper, callback); SingleFetchHelper fetchHelper = new SingleFetchHelper(
new NetworkLoadable<T>(manifestUrl, userAgent, parser), callbackLooper, callback);
fetchHelper.startLoading(); fetchHelper.startLoading();
} }
@ -203,7 +204,7 @@ public class ManifestFetcher<T> implements Loader.Callback {
loader = new Loader("manifestLoader"); loader = new Loader("manifestLoader");
} }
if (!loader.isLoading()) { if (!loader.isLoading()) {
currentLoadable = new ManifestLoadable<T>(manifestUrl, userAgent, contentId, parser); currentLoadable = new NetworkLoadable<T>(manifestUrl, userAgent, parser);
loader.startLoading(currentLoadable, this); loader.startLoading(currentLoadable, this);
notifyManifestRefreshStarted(); notifyManifestRefreshStarted();
} }
@ -287,16 +288,17 @@ public class ManifestFetcher<T> implements Loader.Callback {
private class SingleFetchHelper implements Loader.Callback { private class SingleFetchHelper implements Loader.Callback {
private final NetworkLoadable<T> singleUseLoadable;
private final Looper callbackLooper; private final Looper callbackLooper;
private final ManifestCallback<T> wrappedCallback; private final ManifestCallback<T> wrappedCallback;
private final Loader singleUseLoader; private final Loader singleUseLoader;
private final ManifestLoadable<T> singleUseLoadable;
public SingleFetchHelper(Looper callbackLooper, ManifestCallback<T> wrappedCallback) { public SingleFetchHelper(NetworkLoadable<T> singleUseLoadable, Looper callbackLooper,
ManifestCallback<T> wrappedCallback) {
this.singleUseLoadable = singleUseLoadable;
this.callbackLooper = callbackLooper; this.callbackLooper = callbackLooper;
this.wrappedCallback = wrappedCallback; this.wrappedCallback = wrappedCallback;
singleUseLoader = new Loader("manifestLoader:single"); singleUseLoader = new Loader("manifestLoader:single");
singleUseLoadable = new ManifestLoadable<T>(manifestUrl, userAgent, contentId, parser);
} }
public void startLoading() { public void startLoading() {
@ -308,7 +310,7 @@ public class ManifestFetcher<T> implements Loader.Callback {
try { try {
T result = singleUseLoadable.getResult(); T result = singleUseLoadable.getResult();
onSingleFetchCompleted(result); onSingleFetchCompleted(result);
wrappedCallback.onManifest(contentId, result); wrappedCallback.onSingleManifest(result);
} finally { } finally {
releaseLoader(); releaseLoader();
} }
@ -319,7 +321,7 @@ public class ManifestFetcher<T> implements Loader.Callback {
// This shouldn't ever happen, but handle it anyway. // This shouldn't ever happen, but handle it anyway.
try { try {
IOException exception = new IOException("Load cancelled", new CancellationException()); IOException exception = new IOException("Load cancelled", new CancellationException());
wrappedCallback.onManifestError(contentId, exception); wrappedCallback.onSingleManifestError(exception);
} finally { } finally {
releaseLoader(); releaseLoader();
} }
@ -328,7 +330,7 @@ public class ManifestFetcher<T> implements Loader.Callback {
@Override @Override
public void onLoadError(Loadable loadable, IOException exception) { public void onLoadError(Loadable loadable, IOException exception) {
try { try {
wrappedCallback.onManifestError(contentId, exception); wrappedCallback.onSingleManifestError(exception);
} finally { } finally {
releaseLoader(); releaseLoader();
} }
@ -340,24 +342,4 @@ public class ManifestFetcher<T> implements Loader.Callback {
} }
private static class ManifestLoadable<T> extends NetworkLoadable<T> {
private final String contentId;
private final ManifestParser<T> parser;
public ManifestLoadable(String url, String userAgent, String contentId,
ManifestParser<T> parser) {
super(url, userAgent);
this.contentId = contentId;
this.parser = parser;
}
@Override
protected T parse(String connectionUrl, InputStream inputStream, String inputEncoding)
throws ParserException, IOException {
return parser.parse(inputStream, inputEncoding, contentId, Util.parseBaseUri(connectionUrl));
}
}
} }

View File

@ -1,48 +0,0 @@
/*
* 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.util;
import com.google.android.exoplayer.ParserException;
import android.net.Uri;
import java.io.IOException;
import java.io.InputStream;
/**
* Parses a manifest from an {@link InputStream}.
*
* @param <T> The type of the manifest being parsed.
*/
public interface ManifestParser<T> {
/**
* Parses a manifest from an {@link InputStream}.
*
* @param inputStream The input stream to consume.
* @param inputEncoding The encoding of the input stream. May be null if the input encoding is
* unknown.
* @param contentId The content id to which the manifest corresponds. May be null.
* @param baseUri If the manifest contains relative uris, this is the uri they are relative to.
* May be null.
* @return The parsed manifest.
* @throws IOException If an error occurs reading the data.
* @throws ParserException If an error occurs parsing the data.
*/
T parse(InputStream inputStream, String inputEncoding, String contentId, Uri baseUri)
throws IOException, ParserException;
}

View File

@ -28,13 +28,34 @@ import java.net.URLConnection;
* *
* @param <T> The type of the object being loaded. * @param <T> The type of the object being loaded.
*/ */
public abstract class NetworkLoadable<T> implements Loadable { public final class NetworkLoadable<T> implements Loadable {
/**
* Parses an object from data loaded over the network.
*/
public interface Parser<T> {
/**
* Parses an object from a network response.
*
* @param connectionUrl The source of the response, after any redirection.
* @param inputStream An {@link InputStream} from which the response data can be read.
* @param inputEncoding The encoding of the data, if available.
* @return The parsed object.
* @throws ParserException If an error occurs parsing the data.
* @throws IOException If an error occurs reading data from the stream.
*/
T parse(String connectionUrl, InputStream inputStream, String inputEncoding)
throws ParserException, IOException;
}
public static final int DEFAULT_TIMEOUT_MILLIS = 10000; public static final int DEFAULT_TIMEOUT_MILLIS = 10000;
private final String url; private final String url;
private final String userAgent; private final String userAgent;
private final int timeoutMillis; private final int timeoutMillis;
private final Parser<T> parser;
private volatile T result; private volatile T result;
private volatile boolean isCanceled; private volatile boolean isCanceled;
@ -42,20 +63,23 @@ public abstract class NetworkLoadable<T> implements Loadable {
/** /**
* @param url The url from which the object should be loaded. * @param url The url from which the object should be loaded.
* @param userAgent The user agent to use when requesting the object. * @param userAgent The user agent to use when requesting the object.
* @param parser Parses the object from the network response.
*/ */
public NetworkLoadable(String url, String userAgent) { public NetworkLoadable(String url, String userAgent, Parser<T> parser) {
this(url, userAgent, DEFAULT_TIMEOUT_MILLIS); this(url, userAgent, DEFAULT_TIMEOUT_MILLIS, parser);
} }
/** /**
* @param url The url from which the object should be loaded. * @param url The url from which the object should be loaded.
* @param userAgent The user agent to use when requesting the object. * @param userAgent The user agent to use when requesting the object.
* @param timeoutMillis The desired http timeout in milliseconds. * @param timeoutMillis The desired http timeout in milliseconds.
* @param parser Parses the object from the network response.
*/ */
public NetworkLoadable(String url, String userAgent, int timeoutMillis) { public NetworkLoadable(String url, String userAgent, int timeoutMillis, Parser<T> parser) {
this.url = url; this.url = url;
this.userAgent = userAgent; this.userAgent = userAgent;
this.timeoutMillis = timeoutMillis; this.timeoutMillis = timeoutMillis;
this.parser = parser;
} }
/** /**
@ -85,7 +109,7 @@ public abstract class NetworkLoadable<T> implements Loadable {
URLConnection connection = configureConnection(new URL(url)); URLConnection connection = configureConnection(new URL(url));
inputStream = connection.getInputStream(); inputStream = connection.getInputStream();
inputEncoding = connection.getContentEncoding(); inputEncoding = connection.getContentEncoding();
result = parse(connection.getURL().toString(), inputStream, inputEncoding); result = parser.parse(connection.getURL().toString(), inputStream, inputEncoding);
} finally { } finally {
if (inputStream != null) { if (inputStream != null) {
inputStream.close(); inputStream.close();
@ -93,19 +117,6 @@ public abstract class NetworkLoadable<T> implements Loadable {
} }
} }
/**
* Parses the raw data into an object.
*
* @param connectionUrl The url, after any redirection has taken place.
* @param inputStream An {@link InputStream} from which the raw data can be read.
* @param inputEncoding The encoding of the raw data, if available.
* @return The parsed object.
* @throws ParserException If an error occurs parsing the data.
* @throws IOException If an error occurs reading data from the stream.
*/
protected abstract T parse(String connectionUrl, InputStream inputStream, String inputEncoding)
throws ParserException, IOException;
private URLConnection configureConnection(URL url) throws IOException { private URLConnection configureConnection(URL url) throws IOException {
URLConnection connection = url.openConnection(); URLConnection connection = url.openConnection();
connection.setConnectTimeout(timeoutMillis); connection.setConnectTimeout(timeoutMillis);