Commit 7f70db97 by ojw28

Merge pull request #321 from google/dev

dev -> dev-webm-vp9-opus
parents 430d06d4 006986cc
Showing with 222 additions and 256 deletions
...@@ -206,13 +206,13 @@ public class PlayerActivity extends Activity implements SurfaceHolder.Callback, ...@@ -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);
} }
......
...@@ -52,6 +52,8 @@ import com.google.android.exoplayer.text.webvtt.WebvttParser; ...@@ -52,6 +52,8 @@ import com.google.android.exoplayer.text.webvtt.WebvttParser;
import com.google.android.exoplayer.upstream.BufferPool; import com.google.android.exoplayer.upstream.BufferPool;
import com.google.android.exoplayer.upstream.DataSource; import com.google.android.exoplayer.upstream.DataSource;
import com.google.android.exoplayer.upstream.DefaultBandwidthMeter; import com.google.android.exoplayer.upstream.DefaultBandwidthMeter;
import com.google.android.exoplayer.upstream.DefaultHttpDataSource;
import com.google.android.exoplayer.upstream.HttpDataSource;
import com.google.android.exoplayer.upstream.UriDataSource; import com.google.android.exoplayer.upstream.UriDataSource;
import com.google.android.exoplayer.util.ManifestFetcher; import com.google.android.exoplayer.util.ManifestFetcher;
import com.google.android.exoplayer.util.ManifestFetcher.ManifestCallback; import com.google.android.exoplayer.util.ManifestFetcher.ManifestCallback;
...@@ -93,7 +95,6 @@ public class DashRendererBuilder implements RendererBuilder, ...@@ -93,7 +95,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;
...@@ -101,15 +102,15 @@ public class DashRendererBuilder implements RendererBuilder, ...@@ -101,15 +102,15 @@ public class DashRendererBuilder implements RendererBuilder,
private DemoPlayer player; private DemoPlayer player;
private RendererBuilderCallback callback; private RendererBuilderCallback callback;
private ManifestFetcher<MediaPresentationDescription> manifestFetcher; private ManifestFetcher<MediaPresentationDescription> manifestFetcher;
private HttpDataSource manifestDataSource;
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,16 +121,17 @@ public class DashRendererBuilder implements RendererBuilder, ...@@ -120,16 +121,17 @@ 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, manifestDataSource = new DefaultHttpDataSource(userAgent, null);
userAgent); manifestFetcher = new ManifestFetcher<MediaPresentationDescription>(url, manifestDataSource,
parser);
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(manifestDataSource, manifest.utcTiming,
manifestFetcher.getManifestLoadTimestamp(), this); manifestFetcher.getManifestLoadTimestamp(), this);
} else { } else {
buildRenderers(); buildRenderers();
...@@ -137,7 +139,7 @@ public class DashRendererBuilder implements RendererBuilder, ...@@ -137,7 +139,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);
} }
......
...@@ -29,6 +29,7 @@ import com.google.android.exoplayer.metadata.MetadataTrackRenderer; ...@@ -29,6 +29,7 @@ import com.google.android.exoplayer.metadata.MetadataTrackRenderer;
import com.google.android.exoplayer.text.eia608.Eia608TrackRenderer; import com.google.android.exoplayer.text.eia608.Eia608TrackRenderer;
import com.google.android.exoplayer.upstream.DataSource; import com.google.android.exoplayer.upstream.DataSource;
import com.google.android.exoplayer.upstream.DefaultBandwidthMeter; import com.google.android.exoplayer.upstream.DefaultBandwidthMeter;
import com.google.android.exoplayer.upstream.DefaultHttpDataSource;
import com.google.android.exoplayer.upstream.UriDataSource; import com.google.android.exoplayer.upstream.UriDataSource;
import com.google.android.exoplayer.util.ManifestFetcher; import com.google.android.exoplayer.util.ManifestFetcher;
import com.google.android.exoplayer.util.ManifestFetcher.ManifestCallback; import com.google.android.exoplayer.util.ManifestFetcher.ManifestCallback;
...@@ -45,15 +46,13 @@ public class HlsRendererBuilder implements RendererBuilder, ManifestCallback<Hls ...@@ -45,15 +46,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 +61,17 @@ public class HlsRendererBuilder implements RendererBuilder, ManifestCallback<Hls ...@@ -62,17 +61,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, new DefaultHttpDataSource(userAgent, null), 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);
......
...@@ -42,6 +42,7 @@ import com.google.android.exoplayer.text.ttml.TtmlParser; ...@@ -42,6 +42,7 @@ import com.google.android.exoplayer.text.ttml.TtmlParser;
import com.google.android.exoplayer.upstream.BufferPool; import com.google.android.exoplayer.upstream.BufferPool;
import com.google.android.exoplayer.upstream.DataSource; import com.google.android.exoplayer.upstream.DataSource;
import com.google.android.exoplayer.upstream.DefaultBandwidthMeter; import com.google.android.exoplayer.upstream.DefaultBandwidthMeter;
import com.google.android.exoplayer.upstream.DefaultHttpDataSource;
import com.google.android.exoplayer.upstream.UriDataSource; import com.google.android.exoplayer.upstream.UriDataSource;
import com.google.android.exoplayer.util.ManifestFetcher; import com.google.android.exoplayer.util.ManifestFetcher;
import com.google.android.exoplayer.util.Util; import com.google.android.exoplayer.util.Util;
...@@ -70,7 +71,6 @@ public class SmoothStreamingRendererBuilder implements RendererBuilder, ...@@ -70,7 +71,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 +78,10 @@ public class SmoothStreamingRendererBuilder implements RendererBuilder, ...@@ -78,11 +78,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 +91,18 @@ public class SmoothStreamingRendererBuilder implements RendererBuilder, ...@@ -92,18 +91,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",
url + "/Manifest", userAgent); new DefaultHttpDataSource(userAgent, null), 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);
......
...@@ -276,13 +276,14 @@ public class ChunkSampleSource implements SampleSource, Loader.Callback { ...@@ -276,13 +276,14 @@ public class ChunkSampleSource implements SampleSource, Loader.Callback {
boolean haveSamples = false; boolean haveSamples = false;
if (isPendingReset() || mediaChunks.isEmpty()) { if (isPendingReset() || mediaChunks.isEmpty()) {
// No sample available. // No sample available.
} else if (mediaChunks.getFirst().sampleAvailable()) { } else if (sampleAvailableOrFinishedLastChunk(mediaChunks.getFirst())) {
// There's a sample available to be read from the current chunk. // There's a sample available to be read from the current chunk.
haveSamples = true; haveSamples = true;
} else { } else {
// It may be the case that the current chunk has been fully read but not yet discarded and // It may be the case that the current chunk has been fully read but not yet discarded and
// that the next chunk has an available sample. Return true if so, otherwise false. // that the next chunk has an available sample. Return true if so, otherwise false.
haveSamples = mediaChunks.size() > 1 && mediaChunks.get(1).sampleAvailable(); haveSamples = mediaChunks.size() > 1
&& sampleAvailableOrFinishedLastChunk(mediaChunks.get(1));
} }
if (!haveSamples) { if (!haveSamples) {
...@@ -716,6 +717,10 @@ public class ChunkSampleSource implements SampleSource, Loader.Callback { ...@@ -716,6 +717,10 @@ public class ChunkSampleSource implements SampleSource, Loader.Callback {
return true; return true;
} }
private boolean sampleAvailableOrFinishedLastChunk(MediaChunk chunk) throws IOException {
return chunk.sampleAvailable() || (chunk.isLastChunk() && chunk.isReadFinished());
}
private boolean isMediaChunk(Chunk chunk) { private boolean isMediaChunk(Chunk chunk) {
return chunk instanceof MediaChunk; return chunk instanceof MediaChunk;
} }
......
...@@ -21,8 +21,8 @@ import com.google.android.exoplayer.dash.mpd.SegmentBase.SegmentList; ...@@ -21,8 +21,8 @@ import com.google.android.exoplayer.dash.mpd.SegmentBase.SegmentList;
import com.google.android.exoplayer.dash.mpd.SegmentBase.SegmentTemplate; 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.upstream.NetworkLoadable;
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.Util; import com.google.android.exoplayer.util.Util;
...@@ -44,11 +44,25 @@ import java.util.List; ...@@ -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,17 +73,17 @@ public class MediaPresentationDescriptionParser extends DefaultHandler ...@@ -59,17 +73,17 @@ 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 { throws IOException, ParserException {
try { try {
XmlPullParser xpp = xmlParserFactory.newPullParser(); XmlPullParser xpp = xmlParserFactory.newPullParser();
xpp.setInput(inputStream, inputEncoding); xpp.setInput(inputStream, null);
int eventType = xpp.next(); int eventType = xpp.next();
if (eventType != XmlPullParser.START_TAG || !"MPD".equals(xpp.getName())) { if (eventType != XmlPullParser.START_TAG || !"MPD".equals(xpp.getName())) {
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 ...@@ -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 ...@@ -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 ...@@ -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 ...@@ -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 ...@@ -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 ...@@ -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 ...@@ -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");
......
...@@ -148,7 +148,8 @@ public abstract class SegmentBase { ...@@ -148,7 +148,8 @@ public abstract class SegmentBase {
* @see DashSegmentIndex#getSegmentNum(long) * @see DashSegmentIndex#getSegmentNum(long)
*/ */
public int getSegmentNum(long timeUs) { public int getSegmentNum(long timeUs) {
int lowIndex = getFirstSegmentNum(); final int firstSegmentNum = getFirstSegmentNum();
int lowIndex = firstSegmentNum;
int highIndex = getLastSegmentNum(); int highIndex = getLastSegmentNum();
if (segmentTimeline == null) { if (segmentTimeline == null) {
// All segments are of equal duration (with the possible exception of the last one). // All segments are of equal duration (with the possible exception of the last one).
...@@ -171,7 +172,7 @@ public abstract class SegmentBase { ...@@ -171,7 +172,7 @@ public abstract class SegmentBase {
return midIndex; return midIndex;
} }
} }
return lowIndex - 1; return lowIndex == firstSegmentNum ? lowIndex : highIndex;
} }
} }
......
...@@ -16,10 +16,11 @@ ...@@ -16,10 +16,11 @@
package com.google.android.exoplayer.dash.mpd; package com.google.android.exoplayer.dash.mpd;
import com.google.android.exoplayer.ParserException; import com.google.android.exoplayer.ParserException;
import com.google.android.exoplayer.upstream.HttpDataSource;
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;
import com.google.android.exoplayer.upstream.NetworkLoadable;
import com.google.android.exoplayer.util.Assertions; import com.google.android.exoplayer.util.Assertions;
import com.google.android.exoplayer.util.NetworkLoadable;
import com.google.android.exoplayer.util.Util; import com.google.android.exoplayer.util.Util;
import android.os.SystemClock; import android.os.SystemClock;
...@@ -62,36 +63,34 @@ public class UtcTimingElementResolver implements Loader.Callback { ...@@ -62,36 +63,34 @@ 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 final HttpDataSource httpDataSource;
private static final int TYPE_ISO = 1;
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}.
* *
* @param userAgent A user agent to use should network requests be necessary. * @param httpDataSource A source to use should network requests be necessary.
* @param timingElement The element to resolve. * @param timingElement The element to resolve.
* @param timingElementElapsedRealtime The {@link SystemClock#elapsedRealtime()} timestamp at * @param timingElementElapsedRealtime The {@link SystemClock#elapsedRealtime()} timestamp at
* which the element was obtained. Used if the element contains a timestamp directly. * which the element was obtained. Used if the element contains a timestamp directly.
* @param callback The callback to invoke on resolution or failure. * @param callback The callback to invoke on resolution or failure.
*/ */
public static void resolveTimingElement(String userAgent, UtcTimingElement timingElement, public static void resolveTimingElement(HttpDataSource httpDataSource,
long timingElementElapsedRealtime, UtcTimingCallback callback) { UtcTimingElement timingElement, long timingElementElapsedRealtime,
UtcTimingElementResolver resolver = new UtcTimingElementResolver(userAgent, timingElement, UtcTimingCallback callback) {
UtcTimingElementResolver resolver = new UtcTimingElementResolver(httpDataSource, timingElement,
timingElementElapsedRealtime, callback); timingElementElapsedRealtime, callback);
resolver.resolve(); resolver.resolve();
} }
private UtcTimingElementResolver(String userAgent, UtcTimingElement timingElement, private UtcTimingElementResolver(HttpDataSource httpDataSource, UtcTimingElement timingElement,
long timingElementElapsedRealtime, UtcTimingCallback callback) { long timingElementElapsedRealtime, UtcTimingCallback callback) {
this.userAgent = userAgent; this.httpDataSource = httpDataSource;
this.timingElement = Assertions.checkNotNull(timingElement); this.timingElement = Assertions.checkNotNull(timingElement);
this.timingElementElapsedRealtime = timingElementElapsedRealtime; this.timingElementElapsedRealtime = timingElementElapsedRealtime;
this.callback = Assertions.checkNotNull(callback); this.callback = Assertions.checkNotNull(callback);
...@@ -102,10 +101,10 @@ public class UtcTimingElementResolver implements Loader.Callback { ...@@ -102,10 +101,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 +121,9 @@ public class UtcTimingElementResolver implements Loader.Callback { ...@@ -122,9 +121,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, httpDataSource, parser);
singleUseLoader.startLoading(singleUseLoadable, this); singleUseLoader.startLoading(singleUseLoadable, this);
} }
...@@ -150,32 +149,31 @@ public class UtcTimingElementResolver implements Loader.Callback { ...@@ -150,32 +149,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) { @Override
super(url, userAgent); public Long parse(String connectionUrl, InputStream inputStream) throws ParserException,
this.type = type; IOException {
String firstLine = new BufferedReader(new InputStreamReader(inputStream)).readLine();
try {
return Util.parseXsDateTime(firstLine);
} catch (ParseException e) {
throw new ParserException(e);
}
} }
}
private static class Iso8601Parser implements NetworkLoadable.Parser<Long> {
@Override @Override
protected Long parse(String connectionUrl, InputStream inputStream, String inputEncoding) public Long parse(String connectionUrl, InputStream inputStream) throws ParserException,
throws ParserException, IOException { IOException {
BufferedReader reader = new BufferedReader(new InputStreamReader(inputStream)); String firstLine = new BufferedReader(new InputStreamReader(inputStream)).readLine();
String firstLine = reader.readLine();
try { try {
switch (type) { // TODO: It may be necessary to handle timestamp offsets from UTC.
case TYPE_XS: SimpleDateFormat format = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss'Z'", Locale.US);
return Util.parseXsDateTime(firstLine); return format.parse(firstLine).getTime();
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();
}
} catch (ParseException e) { } catch (ParseException e) {
throw new ParserException(e); throw new ParserException(e);
} }
......
...@@ -433,8 +433,8 @@ public class HlsChunkSource { ...@@ -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 { ...@@ -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));
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);
......
...@@ -18,7 +18,8 @@ package com.google.android.exoplayer.hls; ...@@ -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.upstream.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; ...@@ -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,10 +84,10 @@ public final class HlsPlaylistParser implements ManifestParser<HlsPlaylist> { ...@@ -83,10 +84,10 @@ 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 contentId, Uri baseUri) throws IOException { throws IOException, ParserException {
BufferedReader reader = new BufferedReader((inputEncoding == null) Uri baseUri = Util.parseBaseUri(connectionUrl);
? new InputStreamReader(inputStream) : new InputStreamReader(inputStream, inputEncoding)); BufferedReader reader = new BufferedReader(new InputStreamReader(inputStream));
Queue<String> extraLines = new LinkedList<String>(); Queue<String> extraLines = new LinkedList<String>();
String line; String line;
try { try {
......
...@@ -164,7 +164,7 @@ public class HlsSampleSource implements SampleSource, Loader.Callback { ...@@ -164,7 +164,7 @@ public class HlsSampleSource implements SampleSource, Loader.Callback {
if (!extractors.isEmpty()) { if (!extractors.isEmpty()) {
discardSamplesForDisabledTracks(extractors.getFirst(), downstreamPositionUs); discardSamplesForDisabledTracks(extractors.getFirst(), downstreamPositionUs);
} }
return continueBufferingInternal(); return loadingFinished || continueBufferingInternal();
} }
private boolean continueBufferingInternal() throws IOException { private boolean continueBufferingInternal() throws IOException {
......
...@@ -171,6 +171,9 @@ public final class Mp4Util { ...@@ -171,6 +171,9 @@ public final class Mp4Util {
// loop advance the index by three. // loop advance the index by three.
} else if (data[i - 2] == 0 && data[i - 1] == 0 && data[i] == 1 } else if (data[i - 2] == 0 && data[i - 1] == 0 && data[i] == 1
&& matchesType(data, i + 1, type)) { && matchesType(data, i + 1, type)) {
if (prefixFlags != null) {
clearPrefixFlags(prefixFlags);
}
return i - 2; return i - 2;
} else { } else {
// There isn't a NAL prefix here, but there might be at the next position. We should // There isn't a NAL prefix here, but there might be at the next position. We should
......
...@@ -19,10 +19,11 @@ import com.google.android.exoplayer.ParserException; ...@@ -19,10 +19,11 @@ import com.google.android.exoplayer.ParserException;
import com.google.android.exoplayer.smoothstreaming.SmoothStreamingManifest.ProtectionElement; import com.google.android.exoplayer.smoothstreaming.SmoothStreamingManifest.ProtectionElement;
import com.google.android.exoplayer.smoothstreaming.SmoothStreamingManifest.StreamElement; import com.google.android.exoplayer.smoothstreaming.SmoothStreamingManifest.StreamElement;
import com.google.android.exoplayer.smoothstreaming.SmoothStreamingManifest.TrackElement; import com.google.android.exoplayer.smoothstreaming.SmoothStreamingManifest.TrackElement;
import com.google.android.exoplayer.upstream.NetworkLoadable;
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.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; ...@@ -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 ...@@ -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 { throws IOException, ParserException {
try { try {
XmlPullParser xmlParser = xmlParserFactory.newPullParser(); XmlPullParser xmlParser = xmlParserFactory.newPullParser();
xmlParser.setInput(inputStream, inputEncoding); xmlParser.setInput(inputStream, null);
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);
......
...@@ -43,6 +43,19 @@ public class DataSourceInputStream extends InputStream { ...@@ -43,6 +43,19 @@ public class DataSourceInputStream extends InputStream {
singleByteArray = new byte[1]; singleByteArray = new byte[1];
} }
/**
* Optional call to open the underlying {@link DataSource}.
* <p>
* Calling this method does nothing if the {@link DataSource} is already open. Calling this
* method is optional, since the read and skip methods will automatically open the underlying
* {@link DataSource} if it's not open already.
*
* @throws IOException If an error occurs opening the {@link DataSource}.
*/
public void open() throws IOException {
checkOpened();
}
@Override @Override
public int read() throws IOException { public int read() throws IOException {
read(singleByteArray); read(singleByteArray);
......
...@@ -13,49 +13,57 @@ ...@@ -13,49 +13,57 @@
* See the License for the specific language governing permissions and * See the License for the specific language governing permissions and
* limitations under the License. * limitations under the License.
*/ */
package com.google.android.exoplayer.util; package com.google.android.exoplayer.upstream;
import com.google.android.exoplayer.ParserException; import com.google.android.exoplayer.ParserException;
import com.google.android.exoplayer.upstream.Loader.Loadable; import com.google.android.exoplayer.upstream.Loader.Loadable;
import android.net.Uri;
import java.io.IOException; import java.io.IOException;
import java.io.InputStream; import java.io.InputStream;
import java.net.URL;
import java.net.URLConnection;
/** /**
* A {@link Loadable} for loading an object over the network. * A {@link Loadable} for loading an object over the network.
* *
* @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> {
public static final int DEFAULT_TIMEOUT_MILLIS = 10000; /**
* 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.
* @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) throws ParserException, IOException;
private final String url; }
private final String userAgent;
private final int timeoutMillis; private final DataSpec dataSpec;
private final HttpDataSource httpDataSource;
private final Parser<T> parser;
private volatile T result; private volatile T result;
private volatile boolean isCanceled; private volatile boolean isCanceled;
/** /**
* @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 httpDataSource A {@link HttpDataSource} to use when loading the data.
*/ * @param parser Parses the object from the network response.
public NetworkLoadable(String url, String userAgent) {
this(url, userAgent, DEFAULT_TIMEOUT_MILLIS);
}
/**
* @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.
*/ */
public NetworkLoadable(String url, String userAgent, int timeoutMillis) { public NetworkLoadable(String url, HttpDataSource httpDataSource, Parser<T> parser) {
this.url = url; this.httpDataSource = httpDataSource;
this.userAgent = userAgent; this.parser = parser;
this.timeoutMillis = timeoutMillis; dataSpec = new DataSpec(Uri.parse(url));
} }
/** /**
...@@ -79,41 +87,13 @@ public abstract class NetworkLoadable<T> implements Loadable { ...@@ -79,41 +87,13 @@ public abstract class NetworkLoadable<T> implements Loadable {
@Override @Override
public final void load() throws IOException, InterruptedException { public final void load() throws IOException, InterruptedException {
String inputEncoding; DataSourceInputStream inputStream = new DataSourceInputStream(httpDataSource, dataSpec);
InputStream inputStream = null;
try { try {
URLConnection connection = configureConnection(new URL(url)); inputStream.open();
inputStream = connection.getInputStream(); result = parser.parse(httpDataSource.getUrl(), inputStream);
inputEncoding = connection.getContentEncoding();
result = parse(connection.getURL().toString(), inputStream, inputEncoding);
} finally { } finally {
if (inputStream != null) { inputStream.close();
inputStream.close();
}
} }
} }
/**
* 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);
connection.setReadTimeout(timeoutMillis);
connection.setDoOutput(false);
connection.setRequestProperty("User-Agent", userAgent);
connection.connect();
return connection;
}
} }
...@@ -37,14 +37,14 @@ public final class UriDataSource implements DataSource { ...@@ -37,14 +37,14 @@ public final class UriDataSource implements DataSource {
/** /**
* Constructs a new data source that delegates to a {@link FileDataSource} for file URIs and an * Constructs a new data source that delegates to a {@link FileDataSource} for file URIs and an
* {@link HttpDataSource} for other URIs. * {@link DefaultHttpDataSource} for other URIs.
* *
* @param userAgent The User-Agent string that should be used when requesting remote data. * @param userAgent The User-Agent string that should be used when requesting remote data.
* @param transferListener An optional listener. * @param transferListener An optional listener.
*/ */
public UriDataSource(String userAgent, TransferListener transferListener) { public UriDataSource(String userAgent, TransferListener transferListener) {
this(new FileDataSource(transferListener), this(new FileDataSource(transferListener),
new HttpDataSource(userAgent, null, transferListener)); new DefaultHttpDataSource(userAgent, null, transferListener));
} }
/** /**
......
...@@ -15,9 +15,10 @@ ...@@ -15,9 +15,10 @@
*/ */
package com.google.android.exoplayer.util; package com.google.android.exoplayer.util;
import com.google.android.exoplayer.ParserException; import com.google.android.exoplayer.upstream.HttpDataSource;
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;
import com.google.android.exoplayer.upstream.NetworkLoadable;
import android.os.Handler; import android.os.Handler;
import android.os.Looper; import android.os.Looper;
...@@ -25,11 +26,22 @@ import android.os.SystemClock; ...@@ -25,11 +26,22 @@ 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;
/** /**
* Performs both single and repeated loads of media manifests. * Performs both single and repeated loads of media manifests.
* <p>
* Client code is responsible for ensuring that only one load is taking place at any one time.
* Typical usage of this class is as follows:
* <ol>
* <li>Create an instance.</li>
* <li>Obtain an initial manifest by calling {@link #singleLoad(Looper, ManifestCallback)} and
* waiting for the callback to be invoked.</li>
* <li>For on-demand playbacks, the loader is no longer required. For live playbacks, the loader
* may be required to periodically refresh the manifest. In this case it is injected into any
* components that require it. These components will call {@link #requestRefresh()} on the
* loader whenever a refresh is required.</li>
* </ol>
* *
* @param <T> The type of manifest. * @param <T> The type of manifest.
*/ */
...@@ -58,24 +70,21 @@ public class ManifestFetcher<T> implements Loader.Callback { ...@@ -58,24 +70,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 HttpDataSource httpDataSource;
/* package */ final String userAgent;
private final Handler eventHandler; private final Handler eventHandler;
private final EventListener eventListener; private final EventListener eventListener;
...@@ -83,7 +92,7 @@ public class ManifestFetcher<T> implements Loader.Callback { ...@@ -83,7 +92,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,23 +101,29 @@ public class ManifestFetcher<T> implements Loader.Callback { ...@@ -92,23 +101,29 @@ 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 httpDataSource The {@link HttpDataSource} to use when loading the manifest.
* @param parser A parser to parse the loaded manifest data.
*/
public ManifestFetcher(String manifestUrl, HttpDataSource httpDataSource,
NetworkLoadable.Parser<T> parser) {
this(manifestUrl, httpDataSource, 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 httpDataSource The {@link HttpDataSource} to use when loading the manifest.
* @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, HttpDataSource httpDataSource,
String userAgent, Handler eventHandler, EventListener eventListener) { NetworkLoadable.Parser<T> parser, Handler eventHandler, EventListener eventListener) {
this.parser = parser; this.parser = parser;
this.contentId = contentId;
this.manifestUrl = manifestUrl; this.manifestUrl = manifestUrl;
this.userAgent = userAgent; this.httpDataSource = httpDataSource;
this.eventHandler = eventHandler; this.eventHandler = eventHandler;
this.eventListener = eventListener; this.eventListener = eventListener;
} }
...@@ -130,7 +145,8 @@ public class ManifestFetcher<T> implements Loader.Callback { ...@@ -130,7 +145,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, httpDataSource, parser), callbackLooper, callback);
fetchHelper.startLoading(); fetchHelper.startLoading();
} }
...@@ -203,7 +219,7 @@ public class ManifestFetcher<T> implements Loader.Callback { ...@@ -203,7 +219,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, httpDataSource, parser);
loader.startLoading(currentLoadable, this); loader.startLoading(currentLoadable, this);
notifyManifestRefreshStarted(); notifyManifestRefreshStarted();
} }
...@@ -287,16 +303,17 @@ public class ManifestFetcher<T> implements Loader.Callback { ...@@ -287,16 +303,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 +325,7 @@ public class ManifestFetcher<T> implements Loader.Callback { ...@@ -308,7 +325,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 +336,7 @@ public class ManifestFetcher<T> implements Loader.Callback { ...@@ -319,7 +336,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 +345,7 @@ public class ManifestFetcher<T> implements Loader.Callback { ...@@ -328,7 +345,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 +357,4 @@ public class ManifestFetcher<T> implements Loader.Callback { ...@@ -340,24 +357,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));
}
}
} }
/*
* 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;
}
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