From d2da3bbf8a2dd5af202c89f2817ab1daa14ae613 Mon Sep 17 00:00:00 2001 From: Oliver Woodman Date: Wed, 25 Feb 2015 12:09:06 +0000 Subject: [PATCH] Simplify NetworkLoader/Parser/ManifestParser The only downside of this change is that MediaPresentationDescriptionParser is no longer stateless. --- .../exoplayer/demo/PlayerActivity.java | 6 +- .../demo/player/DashRendererBuilder.java | 13 ++-- .../demo/player/HlsRendererBuilder.java | 10 +-- .../SmoothStreamingRendererBuilder.java | 14 ++-- .../MediaPresentationDescriptionParser.java | 45 +++++++---- .../dash/mpd/UtcTimingElementResolver.java | 56 +++++++------- .../android/exoplayer/hls/HlsChunkSource.java | 14 ++-- .../exoplayer/hls/HlsPlaylistParser.java | 10 ++- .../SmoothStreamingManifestParser.java | 13 ++-- .../exoplayer/util/ManifestFetcher.java | 74 +++++++------------ .../exoplayer/util/ManifestParser.java | 48 ------------ .../exoplayer/util/NetworkLoadable.java | 47 +++++++----- 12 files changed, 151 insertions(+), 199 deletions(-) delete mode 100644 library/src/main/java/com/google/android/exoplayer/util/ManifestParser.java diff --git a/demo/src/main/java/com/google/android/exoplayer/demo/PlayerActivity.java b/demo/src/main/java/com/google/android/exoplayer/demo/PlayerActivity.java index 6f04db8991..e52bb377fd 100644 --- a/demo/src/main/java/com/google/android/exoplayer/demo/PlayerActivity.java +++ b/demo/src/main/java/com/google/android/exoplayer/demo/PlayerActivity.java @@ -206,13 +206,13 @@ public class PlayerActivity extends Activity implements SurfaceHolder.Callback, String userAgent = DemoUtil.getUserAgent(this); switch (contentType) { case DemoUtil.TYPE_SS: - return new SmoothStreamingRendererBuilder(userAgent, contentUri.toString(), contentId, + return new SmoothStreamingRendererBuilder(userAgent, contentUri.toString(), new SmoothStreamingTestMediaDrmCallback(), debugTextView); case DemoUtil.TYPE_DASH: - return new DashRendererBuilder(userAgent, contentUri.toString(), contentId, + return new DashRendererBuilder(userAgent, contentUri.toString(), new WidevineTestMediaDrmCallback(contentId), debugTextView, audioCapabilities); case DemoUtil.TYPE_HLS: - return new HlsRendererBuilder(userAgent, contentUri.toString(), contentId); + return new HlsRendererBuilder(userAgent, contentUri.toString()); default: return new DefaultRendererBuilder(this, contentUri, debugTextView); } diff --git a/demo/src/main/java/com/google/android/exoplayer/demo/player/DashRendererBuilder.java b/demo/src/main/java/com/google/android/exoplayer/demo/player/DashRendererBuilder.java index dd1cf829d9..ab0c316697 100644 --- a/demo/src/main/java/com/google/android/exoplayer/demo/player/DashRendererBuilder.java +++ b/demo/src/main/java/com/google/android/exoplayer/demo/player/DashRendererBuilder.java @@ -93,7 +93,6 @@ public class DashRendererBuilder implements RendererBuilder, private final String userAgent; private final String url; - private final String contentId; private final MediaDrmCallback drmCallback; private final TextView debugTextView; private final AudioCapabilities audioCapabilities; @@ -105,11 +104,10 @@ public class DashRendererBuilder implements RendererBuilder, private MediaPresentationDescription manifest; private long elapsedRealtimeOffset; - public DashRendererBuilder(String userAgent, String url, String contentId, - MediaDrmCallback drmCallback, TextView debugTextView, AudioCapabilities audioCapabilities) { + public DashRendererBuilder(String userAgent, String url, MediaDrmCallback drmCallback, + TextView debugTextView, AudioCapabilities audioCapabilities) { this.userAgent = userAgent; this.url = url; - this.contentId = contentId; this.drmCallback = drmCallback; this.debugTextView = debugTextView; this.audioCapabilities = audioCapabilities; @@ -120,13 +118,12 @@ public class DashRendererBuilder implements RendererBuilder, this.player = player; this.callback = callback; MediaPresentationDescriptionParser parser = new MediaPresentationDescriptionParser(); - manifestFetcher = new ManifestFetcher(parser, contentId, url, - userAgent); + manifestFetcher = new ManifestFetcher(url, userAgent, parser); manifestFetcher.singleLoad(player.getMainHandler().getLooper(), this); } @Override - public void onManifest(String contentId, MediaPresentationDescription manifest) { + public void onSingleManifest(MediaPresentationDescription manifest) { this.manifest = manifest; if (manifest.dynamic && manifest.utcTiming != null) { UtcTimingElementResolver.resolveTimingElement(userAgent, manifest.utcTiming, @@ -137,7 +134,7 @@ public class DashRendererBuilder implements RendererBuilder, } @Override - public void onManifestError(String contentId, IOException e) { + public void onSingleManifestError(IOException e) { callback.onRenderersError(e); } diff --git a/demo/src/main/java/com/google/android/exoplayer/demo/player/HlsRendererBuilder.java b/demo/src/main/java/com/google/android/exoplayer/demo/player/HlsRendererBuilder.java index 326a0689d5..d0c8465bbe 100644 --- a/demo/src/main/java/com/google/android/exoplayer/demo/player/HlsRendererBuilder.java +++ b/demo/src/main/java/com/google/android/exoplayer/demo/player/HlsRendererBuilder.java @@ -45,15 +45,13 @@ public class HlsRendererBuilder implements RendererBuilder, ManifestCallback playlistFetcher = - new ManifestFetcher(parser, contentId, url, userAgent); + new ManifestFetcher(url, userAgent, parser); playlistFetcher.singleLoad(player.getMainHandler().getLooper(), this); } @Override - public void onManifestError(String contentId, IOException e) { + public void onSingleManifestError(IOException e) { callback.onRenderersError(e); } @Override - public void onManifest(String contentId, HlsPlaylist manifest) { + public void onSingleManifest(HlsPlaylist manifest) { DefaultBandwidthMeter bandwidthMeter = new DefaultBandwidthMeter(); DataSource dataSource = new UriDataSource(userAgent, bandwidthMeter); diff --git a/demo/src/main/java/com/google/android/exoplayer/demo/player/SmoothStreamingRendererBuilder.java b/demo/src/main/java/com/google/android/exoplayer/demo/player/SmoothStreamingRendererBuilder.java index 22a342bfc7..1d22f6cc07 100644 --- a/demo/src/main/java/com/google/android/exoplayer/demo/player/SmoothStreamingRendererBuilder.java +++ b/demo/src/main/java/com/google/android/exoplayer/demo/player/SmoothStreamingRendererBuilder.java @@ -70,7 +70,6 @@ public class SmoothStreamingRendererBuilder implements RendererBuilder, private final String userAgent; private final String url; - private final String contentId; private final MediaDrmCallback drmCallback; private final TextView debugTextView; @@ -78,11 +77,10 @@ public class SmoothStreamingRendererBuilder implements RendererBuilder, private RendererBuilderCallback callback; private ManifestFetcher manifestFetcher; - public SmoothStreamingRendererBuilder(String userAgent, String url, String contentId, - MediaDrmCallback drmCallback, TextView debugTextView) { + public SmoothStreamingRendererBuilder(String userAgent, String url, MediaDrmCallback drmCallback, + TextView debugTextView) { this.userAgent = userAgent; this.url = url; - this.contentId = contentId; this.drmCallback = drmCallback; this.debugTextView = debugTextView; } @@ -92,18 +90,18 @@ public class SmoothStreamingRendererBuilder implements RendererBuilder, this.player = player; this.callback = callback; SmoothStreamingManifestParser parser = new SmoothStreamingManifestParser(); - manifestFetcher = new ManifestFetcher(parser, contentId, - url + "/Manifest", userAgent); + manifestFetcher = new ManifestFetcher(url + "/Manifest", userAgent, + parser); manifestFetcher.singleLoad(player.getMainHandler().getLooper(), this); } @Override - public void onManifestError(String contentId, IOException exception) { + public void onSingleManifestError(IOException exception) { callback.onRenderersError(exception); } @Override - public void onManifest(String contentId, SmoothStreamingManifest manifest) { + public void onSingleManifest(SmoothStreamingManifest manifest) { Handler mainHandler = player.getMainHandler(); LoadControl loadControl = new DefaultLoadControl(new BufferPool(BUFFER_SEGMENT_SIZE)); DefaultBandwidthMeter bandwidthMeter = new DefaultBandwidthMeter(mainHandler, player); diff --git a/library/src/main/java/com/google/android/exoplayer/dash/mpd/MediaPresentationDescriptionParser.java b/library/src/main/java/com/google/android/exoplayer/dash/mpd/MediaPresentationDescriptionParser.java index 67806bbb68..2b969a7165 100644 --- a/library/src/main/java/com/google/android/exoplayer/dash/mpd/MediaPresentationDescriptionParser.java +++ b/library/src/main/java/com/google/android/exoplayer/dash/mpd/MediaPresentationDescriptionParser.java @@ -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.SingleSegmentBase; 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.NetworkLoadable; import com.google.android.exoplayer.util.Util; import android.net.Uri; @@ -44,11 +44,25 @@ import java.util.List; * A parser of media presentation description files. */ public class MediaPresentationDescriptionParser extends DefaultHandler - implements ManifestParser { + implements NetworkLoadable.Parser { + private final String contentId; private final XmlPullParserFactory xmlParserFactory; + /** + * Equivalent to calling {@code new MediaPresentationDescriptionParser(null)}. + */ 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 { xmlParserFactory = XmlPullParserFactory.newInstance(); } catch (XmlPullParserException e) { @@ -59,8 +73,8 @@ public class MediaPresentationDescriptionParser extends DefaultHandler // MPD parsing. @Override - public MediaPresentationDescription parse(InputStream inputStream, String inputEncoding, - String contentId, Uri baseUrl) throws IOException, ParserException { + public MediaPresentationDescription parse(String connectionUrl, InputStream inputStream, + String inputEncoding) throws IOException, ParserException { try { XmlPullParser xpp = xmlParserFactory.newPullParser(); xpp.setInput(inputStream, inputEncoding); @@ -69,7 +83,7 @@ public class MediaPresentationDescriptionParser extends DefaultHandler throw new ParserException( "inputStream does not contain a valid media presentation description"); } - return parseMediaPresentationDescription(xpp, contentId, baseUrl); + return parseMediaPresentationDescription(xpp, Util.parseBaseUri(connectionUrl)); } catch (XmlPullParserException e) { throw new ParserException(e); } catch (ParseException e) { @@ -78,7 +92,7 @@ public class MediaPresentationDescriptionParser extends DefaultHandler } 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 durationMs = parseDuration(xpp, "mediaPresentationDuration", -1); long minBufferTimeMs = parseDuration(xpp, "minBufferTime", -1); @@ -97,7 +111,7 @@ public class MediaPresentationDescriptionParser extends DefaultHandler } else if (isStartTag(xpp, "UTCTiming")) { utcTiming = parseUtcTiming(xpp); } else if (isStartTag(xpp, "Period")) { - periods.add(parsePeriod(xpp, contentId, baseUrl, durationMs)); + periods.add(parsePeriod(xpp, baseUrl, durationMs)); } } while (!isEndTag(xpp, "MPD")); @@ -123,7 +137,7 @@ public class MediaPresentationDescriptionParser extends DefaultHandler 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 { String id = xpp.getAttributeValue(null, "id"); long startMs = parseDuration(xpp, "start", 0); @@ -135,7 +149,7 @@ public class MediaPresentationDescriptionParser extends DefaultHandler if (isStartTag(xpp, "BaseURL")) { baseUrl = parseBaseUrl(xpp, baseUrl); } else if (isStartTag(xpp, "AdaptationSet")) { - adaptationSets.add(parseAdaptationSet(xpp, contentId, baseUrl, startMs, durationMs, + adaptationSets.add(parseAdaptationSet(xpp, baseUrl, startMs, durationMs, segmentBase)); } else if (isStartTag(xpp, "SegmentBase")) { segmentBase = parseSegmentBase(xpp, baseUrl, null); @@ -156,9 +170,8 @@ public class MediaPresentationDescriptionParser extends DefaultHandler // AdaptationSet parsing. - protected AdaptationSet parseAdaptationSet(XmlPullParser xpp, String contentId, Uri baseUrl, - long periodStartMs, long periodDurationMs, SegmentBase segmentBase) - throws XmlPullParserException, IOException { + protected AdaptationSet parseAdaptationSet(XmlPullParser xpp, Uri baseUrl, long periodStartMs, + long periodDurationMs, SegmentBase segmentBase) throws XmlPullParserException, IOException { String mimeType = xpp.getAttributeValue(null, "mimeType"); String language = xpp.getAttributeValue(null, "lang"); @@ -181,7 +194,7 @@ public class MediaPresentationDescriptionParser extends DefaultHandler contentType = checkAdaptationSetTypeConsistency(contentType, parseAdaptationSetType(xpp.getAttributeValue(null, "contentType"))); } else if (isStartTag(xpp, "Representation")) { - Representation representation = parseRepresentation(xpp, contentId, baseUrl, periodStartMs, + Representation representation = parseRepresentation(xpp, baseUrl, periodStartMs, periodDurationMs, mimeType, language, segmentBase); contentType = checkAdaptationSetTypeConsistency(contentType, parseAdaptationSetTypeFromMimeType(representation.format.mimeType)); @@ -274,9 +287,9 @@ public class MediaPresentationDescriptionParser extends DefaultHandler // Representation parsing. - protected Representation parseRepresentation(XmlPullParser xpp, String contentId, Uri baseUrl, - long periodStartMs, long periodDurationMs, String mimeType, String language, - SegmentBase segmentBase) throws XmlPullParserException, IOException { + protected Representation parseRepresentation(XmlPullParser xpp, Uri baseUrl, long periodStartMs, + long periodDurationMs, String mimeType, String language, SegmentBase segmentBase) + throws XmlPullParserException, IOException { String id = xpp.getAttributeValue(null, "id"); int bandwidth = parseInt(xpp, "bandwidth"); int audioSamplingRate = parseInt(xpp, "audioSamplingRate"); diff --git a/library/src/main/java/com/google/android/exoplayer/dash/mpd/UtcTimingElementResolver.java b/library/src/main/java/com/google/android/exoplayer/dash/mpd/UtcTimingElementResolver.java index 3c457c6fd7..1220bdd89d 100644 --- a/library/src/main/java/com/google/android/exoplayer/dash/mpd/UtcTimingElementResolver.java +++ b/library/src/main/java/com/google/android/exoplayer/dash/mpd/UtcTimingElementResolver.java @@ -62,16 +62,13 @@ public class UtcTimingElementResolver implements Loader.Callback { 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 UtcTimingElement timingElement; private final long timingElementElapsedRealtime; private final UtcTimingCallback callback; private Loader singleUseLoader; - private HttpTimestampLoadable singleUseLoadable; + private NetworkLoadable singleUseLoadable; /** * Resolves a {@link UtcTimingElement}. @@ -102,10 +99,10 @@ public class UtcTimingElementResolver implements Loader.Callback { if (Util.areEqual(scheme, "urn:mpeg:dash:utc:direct:2012")) { resolveDirect(); } 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") || Util.areEqual(scheme, "urn:mpeg:dash:utc:http-xsdate:2014")) { - resolveHttp(TYPE_XS); + resolveHttp(new XsDateTimeParser()); } else { // Unsupported 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 parser) { singleUseLoader = new Loader("utctiming"); - singleUseLoadable = new HttpTimestampLoadable(timingElement.value, userAgent, type); + singleUseLoadable = new NetworkLoadable(timingElement.value, userAgent, parser); singleUseLoader.startLoading(singleUseLoadable, this); } @@ -150,32 +147,31 @@ public class UtcTimingElementResolver implements Loader.Callback { singleUseLoader.release(); } - private static class HttpTimestampLoadable extends NetworkLoadable { - - private final int type; - - public HttpTimestampLoadable(String url, String userAgent, int type) { - super(url, userAgent); - this.type = type; - } + private static class XsDateTimeParser implements NetworkLoadable.Parser { @Override - protected Long parse(String connectionUrl, InputStream inputStream, String inputEncoding) + public Long parse(String connectionUrl, InputStream inputStream, String inputEncoding) throws ParserException, IOException { - BufferedReader reader = new BufferedReader(new InputStreamReader(inputStream)); - String firstLine = reader.readLine(); + String firstLine = new BufferedReader(new InputStreamReader(inputStream)).readLine(); try { - switch (type) { - case TYPE_XS: - return Util.parseXsDateTime(firstLine); - 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. - throw new RuntimeException(); - } + return Util.parseXsDateTime(firstLine); + } catch (ParseException e) { + throw new ParserException(e); + } + } + + } + + private static class Iso8601Parser implements NetworkLoadable.Parser { + + @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) { throw new ParserException(e); } diff --git a/library/src/main/java/com/google/android/exoplayer/hls/HlsChunkSource.java b/library/src/main/java/com/google/android/exoplayer/hls/HlsChunkSource.java index dc70320f0d..f2469f294f 100644 --- a/library/src/main/java/com/google/android/exoplayer/hls/HlsChunkSource.java +++ b/library/src/main/java/com/google/android/exoplayer/hls/HlsChunkSource.java @@ -433,8 +433,8 @@ public class HlsChunkSource { private MediaPlaylistChunk newMediaPlaylistChunk(int variantIndex) { Uri mediaPlaylistUri = Util.getMergedUri(baseUri, enabledVariants[variantIndex].url); DataSpec dataSpec = new DataSpec(mediaPlaylistUri, 0, C.LENGTH_UNBOUNDED, null); - Uri baseUri = Util.parseBaseUri(mediaPlaylistUri.toString()); - return new MediaPlaylistChunk(variantIndex, upstreamDataSource, dataSpec, baseUri); + return new MediaPlaylistChunk(variantIndex, upstreamDataSource, dataSpec, + mediaPlaylistUri.toString()); } private EncryptionKeyChunk newEncryptionKeyChunk(Uri keyUri, String iv) { @@ -546,19 +546,19 @@ public class HlsChunkSource { @SuppressWarnings("hiding") /* package */ final int variantIndex; - private final Uri playlistBaseUri; + private final String playlistUrl; public MediaPlaylistChunk(int variantIndex, DataSource dataSource, DataSpec dataSpec, - Uri playlistBaseUri) { + String playlistUrl) { super(dataSource, dataSpec, scratchSpace); this.variantIndex = variantIndex; - this.playlistBaseUri = playlistBaseUri; + this.playlistUrl = playlistUrl; } @Override protected void consume(byte[] data, int limit) throws IOException { - HlsPlaylist playlist = playlistParser.parse(new ByteArrayInputStream(data, 0, limit), - null, null, playlistBaseUri); + HlsPlaylist playlist = playlistParser.parse(playlistUrl, + new ByteArrayInputStream(data, 0, limit), null); Assertions.checkState(playlist.type == HlsPlaylist.TYPE_MEDIA); HlsMediaPlaylist mediaPlaylist = (HlsMediaPlaylist) playlist; setMediaPlaylist(variantIndex, mediaPlaylist); diff --git a/library/src/main/java/com/google/android/exoplayer/hls/HlsPlaylistParser.java b/library/src/main/java/com/google/android/exoplayer/hls/HlsPlaylistParser.java index 0205364718..d9d1e6c415 100644 --- a/library/src/main/java/com/google/android/exoplayer/hls/HlsPlaylistParser.java +++ b/library/src/main/java/com/google/android/exoplayer/hls/HlsPlaylistParser.java @@ -18,7 +18,8 @@ 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; +import com.google.android.exoplayer.util.NetworkLoadable; +import com.google.android.exoplayer.util.Util; import android.net.Uri; @@ -36,7 +37,7 @@ import java.util.regex.Pattern; /** * HLS playlists parsing logic. */ -public final class HlsPlaylistParser implements ManifestParser { +public final class HlsPlaylistParser implements NetworkLoadable.Parser { private static final String VERSION_TAG = "#EXT-X-VERSION"; @@ -83,8 +84,9 @@ public final class HlsPlaylistParser implements ManifestParser { Pattern.compile(IV_ATTR + "=([^,.*]+)"); @Override - public HlsPlaylist parse(InputStream inputStream, String inputEncoding, - String contentId, Uri baseUri) throws IOException { + public HlsPlaylist parse(String connectionUrl, InputStream inputStream, String inputEncoding) + throws IOException, ParserException { + Uri baseUri = Util.parseBaseUri(connectionUrl); BufferedReader reader = new BufferedReader((inputEncoding == null) ? new InputStreamReader(inputStream) : new InputStreamReader(inputStream, inputEncoding)); Queue extraLines = new LinkedList(); diff --git a/library/src/main/java/com/google/android/exoplayer/smoothstreaming/SmoothStreamingManifestParser.java b/library/src/main/java/com/google/android/exoplayer/smoothstreaming/SmoothStreamingManifestParser.java index 20aea8ad32..47a90da919 100644 --- a/library/src/main/java/com/google/android/exoplayer/smoothstreaming/SmoothStreamingManifestParser.java +++ b/library/src/main/java/com/google/android/exoplayer/smoothstreaming/SmoothStreamingManifestParser.java @@ -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.util.Assertions; 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.NetworkLoadable; +import com.google.android.exoplayer.util.Util; import android.net.Uri; import android.util.Base64; @@ -45,7 +46,8 @@ import java.util.UUID; * @see * IIS Smooth Streaming Client Manifest Format */ -public class SmoothStreamingManifestParser implements ManifestParser { +public class SmoothStreamingManifestParser implements + NetworkLoadable.Parser { private final XmlPullParserFactory xmlParserFactory; @@ -58,12 +60,13 @@ public class SmoothStreamingManifestParser implements ManifestParser implements Loader.Callback { /** * Invoked when the load has successfully completed. * - * @param contentId The content id of the media. * @param manifest The loaded manifest. */ - void onManifest(String contentId, T manifest); + void onSingleManifest(T manifest); /** * Invoked when the load has failed. * - * @param contentId The content id of the media. * @param e The cause of the failure. */ - void onManifestError(String contentId, IOException e); + void onSingleManifestError(IOException e); } - /* package */ final ManifestParser parser; - /* package */ final String contentId; - /* package */ final String userAgent; + private final NetworkLoadable.Parser parser; + private final String userAgent; private final Handler eventHandler; private final EventListener eventListener; @@ -83,7 +78,7 @@ public class ManifestFetcher implements Loader.Callback { private int enabledCount; private Loader loader; - private ManifestLoadable currentLoadable; + private NetworkLoadable currentLoadable; private int loadExceptionCount; private long loadExceptionTimestamp; @@ -92,21 +87,26 @@ public class ManifestFetcher implements Loader.Callback { private volatile T manifest; private volatile long manifestLoadTimestamp; - public ManifestFetcher(ManifestParser parser, String contentId, String manifestUrl, - String userAgent) { - this(parser, contentId, manifestUrl, userAgent, null, null); + /** + * @param manifestUrl The manifest location. + * @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 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 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 parser, String contentId, String manifestUrl, - String userAgent, Handler eventHandler, EventListener eventListener) { + public ManifestFetcher(String manifestUrl, String userAgent, NetworkLoadable.Parser parser, + Handler eventHandler, EventListener eventListener) { this.parser = parser; - this.contentId = contentId; this.manifestUrl = manifestUrl; this.userAgent = userAgent; this.eventHandler = eventHandler; @@ -130,7 +130,8 @@ public class ManifestFetcher implements Loader.Callback { * @param callback The callback to receive the result. */ public void singleLoad(Looper callbackLooper, final ManifestCallback callback) { - SingleFetchHelper fetchHelper = new SingleFetchHelper(callbackLooper, callback); + SingleFetchHelper fetchHelper = new SingleFetchHelper( + new NetworkLoadable(manifestUrl, userAgent, parser), callbackLooper, callback); fetchHelper.startLoading(); } @@ -203,7 +204,7 @@ public class ManifestFetcher implements Loader.Callback { loader = new Loader("manifestLoader"); } if (!loader.isLoading()) { - currentLoadable = new ManifestLoadable(manifestUrl, userAgent, contentId, parser); + currentLoadable = new NetworkLoadable(manifestUrl, userAgent, parser); loader.startLoading(currentLoadable, this); notifyManifestRefreshStarted(); } @@ -287,16 +288,17 @@ public class ManifestFetcher implements Loader.Callback { private class SingleFetchHelper implements Loader.Callback { + private final NetworkLoadable singleUseLoadable; private final Looper callbackLooper; private final ManifestCallback wrappedCallback; private final Loader singleUseLoader; - private final ManifestLoadable singleUseLoadable; - public SingleFetchHelper(Looper callbackLooper, ManifestCallback wrappedCallback) { + public SingleFetchHelper(NetworkLoadable singleUseLoadable, Looper callbackLooper, + ManifestCallback wrappedCallback) { + this.singleUseLoadable = singleUseLoadable; this.callbackLooper = callbackLooper; this.wrappedCallback = wrappedCallback; singleUseLoader = new Loader("manifestLoader:single"); - singleUseLoadable = new ManifestLoadable(manifestUrl, userAgent, contentId, parser); } public void startLoading() { @@ -308,7 +310,7 @@ public class ManifestFetcher implements Loader.Callback { try { T result = singleUseLoadable.getResult(); onSingleFetchCompleted(result); - wrappedCallback.onManifest(contentId, result); + wrappedCallback.onSingleManifest(result); } finally { releaseLoader(); } @@ -319,7 +321,7 @@ public class ManifestFetcher implements Loader.Callback { // This shouldn't ever happen, but handle it anyway. try { IOException exception = new IOException("Load cancelled", new CancellationException()); - wrappedCallback.onManifestError(contentId, exception); + wrappedCallback.onSingleManifestError(exception); } finally { releaseLoader(); } @@ -328,7 +330,7 @@ public class ManifestFetcher implements Loader.Callback { @Override public void onLoadError(Loadable loadable, IOException exception) { try { - wrappedCallback.onManifestError(contentId, exception); + wrappedCallback.onSingleManifestError(exception); } finally { releaseLoader(); } @@ -340,24 +342,4 @@ public class ManifestFetcher implements Loader.Callback { } - private static class ManifestLoadable extends NetworkLoadable { - - private final String contentId; - private final ManifestParser parser; - - public ManifestLoadable(String url, String userAgent, String contentId, - ManifestParser 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)); - } - - } - } diff --git a/library/src/main/java/com/google/android/exoplayer/util/ManifestParser.java b/library/src/main/java/com/google/android/exoplayer/util/ManifestParser.java deleted file mode 100644 index ba997a9f77..0000000000 --- a/library/src/main/java/com/google/android/exoplayer/util/ManifestParser.java +++ /dev/null @@ -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 The type of the manifest being parsed. - */ -public interface ManifestParser { - - /** - * 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; - -} diff --git a/library/src/main/java/com/google/android/exoplayer/util/NetworkLoadable.java b/library/src/main/java/com/google/android/exoplayer/util/NetworkLoadable.java index 676e2380ef..9e38795e7d 100644 --- a/library/src/main/java/com/google/android/exoplayer/util/NetworkLoadable.java +++ b/library/src/main/java/com/google/android/exoplayer/util/NetworkLoadable.java @@ -28,13 +28,34 @@ import java.net.URLConnection; * * @param The type of the object being loaded. */ -public abstract class NetworkLoadable implements Loadable { +public final class NetworkLoadable implements Loadable { + + /** + * Parses an object from data loaded over the network. + */ + public interface Parser { + + /** + * 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; private final String url; private final String userAgent; private final int timeoutMillis; + private final Parser parser; private volatile T result; private volatile boolean isCanceled; @@ -42,20 +63,23 @@ public abstract class NetworkLoadable implements Loadable { /** * @param url The url from which the object should be loaded. * @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) { - this(url, userAgent, DEFAULT_TIMEOUT_MILLIS); + public NetworkLoadable(String url, String userAgent, Parser parser) { + this(url, userAgent, DEFAULT_TIMEOUT_MILLIS, parser); } /** * @param url The url from which the object should be loaded. * @param userAgent The user agent to use when requesting the object. * @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 parser) { this.url = url; this.userAgent = userAgent; this.timeoutMillis = timeoutMillis; + this.parser = parser; } /** @@ -85,7 +109,7 @@ public abstract class NetworkLoadable implements Loadable { URLConnection connection = configureConnection(new URL(url)); inputStream = connection.getInputStream(); inputEncoding = connection.getContentEncoding(); - result = parse(connection.getURL().toString(), inputStream, inputEncoding); + result = parser.parse(connection.getURL().toString(), inputStream, inputEncoding); } finally { if (inputStream != null) { inputStream.close(); @@ -93,19 +117,6 @@ public abstract class NetworkLoadable 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 { URLConnection connection = url.openConnection(); connection.setConnectTimeout(timeoutMillis);