mirror of
https://github.com/androidx/media.git
synced 2025-04-30 06:46:50 +08:00
Make NetworkLoadable use an injected HttpDataSource.
Note: I'm fairly confident that NetworkLoadable.Parser implementations can live without the inputEncoding being specified. But not completely 100%... Issue: #311 Issue: #56
This commit is contained in:
parent
2ce17b601f
commit
006986cc58
@ -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;
|
||||||
@ -100,6 +102,7 @@ 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;
|
||||||
@ -118,7 +121,9 @@ 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>(url, userAgent, parser);
|
manifestDataSource = new DefaultHttpDataSource(userAgent, null);
|
||||||
|
manifestFetcher = new ManifestFetcher<MediaPresentationDescription>(url, manifestDataSource,
|
||||||
|
parser);
|
||||||
manifestFetcher.singleLoad(player.getMainHandler().getLooper(), this);
|
manifestFetcher.singleLoad(player.getMainHandler().getLooper(), this);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -126,7 +131,7 @@ public class DashRendererBuilder implements RendererBuilder,
|
|||||||
public void onSingleManifest(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();
|
||||||
|
@ -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;
|
||||||
@ -60,7 +61,7 @@ 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>(url, userAgent, parser);
|
new ManifestFetcher<HlsPlaylist>(url, new DefaultHttpDataSource(userAgent, null), parser);
|
||||||
playlistFetcher.singleLoad(player.getMainHandler().getLooper(), this);
|
playlistFetcher.singleLoad(player.getMainHandler().getLooper(), this);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -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;
|
||||||
@ -90,8 +91,8 @@ 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>(url + "/Manifest", userAgent,
|
manifestFetcher = new ManifestFetcher<SmoothStreamingManifest>(url + "/Manifest",
|
||||||
parser);
|
new DefaultHttpDataSource(userAgent, null), parser);
|
||||||
manifestFetcher.singleLoad(player.getMainHandler().getLooper(), this);
|
manifestFetcher.singleLoad(player.getMainHandler().getLooper(), this);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -21,9 +21,9 @@ 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.MimeTypes;
|
import com.google.android.exoplayer.util.MimeTypes;
|
||||||
import com.google.android.exoplayer.util.NetworkLoadable;
|
|
||||||
import com.google.android.exoplayer.util.Util;
|
import com.google.android.exoplayer.util.Util;
|
||||||
|
|
||||||
import android.net.Uri;
|
import android.net.Uri;
|
||||||
@ -73,11 +73,11 @@ public class MediaPresentationDescriptionParser extends DefaultHandler
|
|||||||
// MPD parsing.
|
// MPD parsing.
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public MediaPresentationDescription parse(String connectionUrl, InputStream inputStream,
|
public MediaPresentationDescription parse(String connectionUrl, InputStream inputStream)
|
||||||
String inputEncoding) 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(
|
||||||
|
@ -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,7 +63,7 @@ public class UtcTimingElementResolver implements Loader.Callback {
|
|||||||
void onTimestampError(UtcTimingElement utcTiming, IOException e);
|
void onTimestampError(UtcTimingElement utcTiming, IOException e);
|
||||||
}
|
}
|
||||||
|
|
||||||
private final String userAgent;
|
private final HttpDataSource httpDataSource;
|
||||||
private final UtcTimingElement timingElement;
|
private final UtcTimingElement timingElement;
|
||||||
private final long timingElementElapsedRealtime;
|
private final long timingElementElapsedRealtime;
|
||||||
private final UtcTimingCallback callback;
|
private final UtcTimingCallback callback;
|
||||||
@ -73,22 +74,23 @@ public class UtcTimingElementResolver implements Loader.Callback {
|
|||||||
/**
|
/**
|
||||||
* 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);
|
||||||
@ -121,7 +123,7 @@ public class UtcTimingElementResolver implements Loader.Callback {
|
|||||||
|
|
||||||
private void resolveHttp(NetworkLoadable.Parser<Long> parser) {
|
private void resolveHttp(NetworkLoadable.Parser<Long> parser) {
|
||||||
singleUseLoader = new Loader("utctiming");
|
singleUseLoader = new Loader("utctiming");
|
||||||
singleUseLoadable = new NetworkLoadable<Long>(timingElement.value, userAgent, parser);
|
singleUseLoadable = new NetworkLoadable<Long>(timingElement.value, httpDataSource, parser);
|
||||||
singleUseLoader.startLoading(singleUseLoadable, this);
|
singleUseLoader.startLoading(singleUseLoadable, this);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -150,8 +152,8 @@ public class UtcTimingElementResolver implements Loader.Callback {
|
|||||||
private static class XsDateTimeParser implements NetworkLoadable.Parser<Long> {
|
private static class XsDateTimeParser implements NetworkLoadable.Parser<Long> {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Long parse(String connectionUrl, InputStream inputStream, String inputEncoding)
|
public Long parse(String connectionUrl, InputStream inputStream) throws ParserException,
|
||||||
throws ParserException, IOException {
|
IOException {
|
||||||
String firstLine = new BufferedReader(new InputStreamReader(inputStream)).readLine();
|
String firstLine = new BufferedReader(new InputStreamReader(inputStream)).readLine();
|
||||||
try {
|
try {
|
||||||
return Util.parseXsDateTime(firstLine);
|
return Util.parseXsDateTime(firstLine);
|
||||||
@ -165,8 +167,8 @@ public class UtcTimingElementResolver implements Loader.Callback {
|
|||||||
private static class Iso8601Parser implements NetworkLoadable.Parser<Long> {
|
private static class Iso8601Parser implements NetworkLoadable.Parser<Long> {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Long parse(String connectionUrl, InputStream inputStream, String inputEncoding)
|
public Long parse(String connectionUrl, InputStream inputStream) throws ParserException,
|
||||||
throws ParserException, IOException {
|
IOException {
|
||||||
String firstLine = new BufferedReader(new InputStreamReader(inputStream)).readLine();
|
String firstLine = new BufferedReader(new InputStreamReader(inputStream)).readLine();
|
||||||
try {
|
try {
|
||||||
// TODO: It may be necessary to handle timestamp offsets from UTC.
|
// TODO: It may be necessary to handle timestamp offsets from UTC.
|
||||||
|
@ -558,7 +558,7 @@ public class HlsChunkSource {
|
|||||||
@Override
|
@Override
|
||||||
protected void consume(byte[] data, int limit) throws IOException {
|
protected void consume(byte[] data, int limit) throws IOException {
|
||||||
HlsPlaylist playlist = playlistParser.parse(playlistUrl,
|
HlsPlaylist playlist = playlistParser.parse(playlistUrl,
|
||||||
new ByteArrayInputStream(data, 0, limit), null);
|
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,7 @@ 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.NetworkLoadable;
|
import com.google.android.exoplayer.upstream.NetworkLoadable;
|
||||||
import com.google.android.exoplayer.util.Util;
|
import com.google.android.exoplayer.util.Util;
|
||||||
|
|
||||||
import android.net.Uri;
|
import android.net.Uri;
|
||||||
@ -84,11 +84,10 @@ public final class HlsPlaylistParser implements NetworkLoadable.Parser<HlsPlayli
|
|||||||
Pattern.compile(IV_ATTR + "=([^,.*]+)");
|
Pattern.compile(IV_ATTR + "=([^,.*]+)");
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public HlsPlaylist parse(String connectionUrl, InputStream inputStream, String inputEncoding)
|
public HlsPlaylist parse(String connectionUrl, InputStream inputStream)
|
||||||
throws IOException, ParserException {
|
throws IOException, ParserException {
|
||||||
Uri baseUri = Util.parseBaseUri(connectionUrl);
|
Uri baseUri = Util.parseBaseUri(connectionUrl);
|
||||||
BufferedReader reader = new BufferedReader((inputEncoding == null)
|
BufferedReader reader = new BufferedReader(new InputStreamReader(inputStream));
|
||||||
? new InputStreamReader(inputStream) : new InputStreamReader(inputStream, inputEncoding));
|
|
||||||
Queue<String> extraLines = new LinkedList<String>();
|
Queue<String> extraLines = new LinkedList<String>();
|
||||||
String line;
|
String line;
|
||||||
try {
|
try {
|
||||||
|
@ -19,10 +19,10 @@ 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.MimeTypes;
|
import com.google.android.exoplayer.util.MimeTypes;
|
||||||
import com.google.android.exoplayer.util.NetworkLoadable;
|
|
||||||
import com.google.android.exoplayer.util.Util;
|
import com.google.android.exoplayer.util.Util;
|
||||||
|
|
||||||
import android.net.Uri;
|
import android.net.Uri;
|
||||||
@ -60,11 +60,11 @@ public class SmoothStreamingManifestParser implements
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public SmoothStreamingManifest parse(String connectionUrl, InputStream inputStream,
|
public SmoothStreamingManifest parse(String connectionUrl, InputStream inputStream)
|
||||||
String inputEncoding) 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,
|
SmoothStreamMediaParser smoothStreamMediaParser = new SmoothStreamMediaParser(null,
|
||||||
Util.parseBaseUri(connectionUrl));
|
Util.parseBaseUri(connectionUrl));
|
||||||
return (SmoothStreamingManifest) smoothStreamMediaParser.parse(xmlParser);
|
return (SmoothStreamingManifest) smoothStreamMediaParser.parse(xmlParser);
|
||||||
|
@ -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);
|
||||||
|
@ -0,0 +1,358 @@
|
|||||||
|
/*
|
||||||
|
* 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.upstream;
|
||||||
|
|
||||||
|
import com.google.android.exoplayer.C;
|
||||||
|
import com.google.android.exoplayer.util.Assertions;
|
||||||
|
import com.google.android.exoplayer.util.Predicate;
|
||||||
|
|
||||||
|
import android.text.TextUtils;
|
||||||
|
import android.util.Log;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.io.InputStream;
|
||||||
|
import java.net.HttpURLConnection;
|
||||||
|
import java.net.URL;
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.regex.Matcher;
|
||||||
|
import java.util.regex.Pattern;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A {@link HttpDataSource} that uses Android's {@link HttpURLConnection}.
|
||||||
|
*/
|
||||||
|
public class DefaultHttpDataSource implements HttpDataSource {
|
||||||
|
|
||||||
|
public static final int DEFAULT_CONNECT_TIMEOUT_MILLIS = 8 * 1000;
|
||||||
|
public static final int DEFAULT_READ_TIMEOUT_MILLIS = 8 * 1000;
|
||||||
|
|
||||||
|
private static final String TAG = "HttpDataSource";
|
||||||
|
private static final Pattern CONTENT_RANGE_HEADER =
|
||||||
|
Pattern.compile("^bytes (\\d+)-(\\d+)/(\\d+)$");
|
||||||
|
|
||||||
|
private final int connectTimeoutMillis;
|
||||||
|
private final int readTimeoutMillis;
|
||||||
|
private final String userAgent;
|
||||||
|
private final Predicate<String> contentTypePredicate;
|
||||||
|
private final HashMap<String, String> requestProperties;
|
||||||
|
private final TransferListener listener;
|
||||||
|
|
||||||
|
private DataSpec dataSpec;
|
||||||
|
private HttpURLConnection connection;
|
||||||
|
private InputStream inputStream;
|
||||||
|
private boolean opened;
|
||||||
|
|
||||||
|
private long dataLength;
|
||||||
|
private long bytesRead;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param userAgent The User-Agent string that should be used.
|
||||||
|
* @param contentTypePredicate An optional {@link Predicate}. If a content type is
|
||||||
|
* rejected by the predicate then a {@link HttpDataSource.InvalidContentTypeException} is
|
||||||
|
* thrown from {@link #open(DataSpec)}.
|
||||||
|
*/
|
||||||
|
public DefaultHttpDataSource(String userAgent, Predicate<String> contentTypePredicate) {
|
||||||
|
this(userAgent, contentTypePredicate, null);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param userAgent The User-Agent string that should be used.
|
||||||
|
* @param contentTypePredicate An optional {@link Predicate}. If a content type is
|
||||||
|
* rejected by the predicate then a {@link HttpDataSource.InvalidContentTypeException} is
|
||||||
|
* thrown from {@link #open(DataSpec)}.
|
||||||
|
* @param listener An optional listener.
|
||||||
|
*/
|
||||||
|
public DefaultHttpDataSource(String userAgent, Predicate<String> contentTypePredicate,
|
||||||
|
TransferListener listener) {
|
||||||
|
this(userAgent, contentTypePredicate, listener, DEFAULT_CONNECT_TIMEOUT_MILLIS,
|
||||||
|
DEFAULT_READ_TIMEOUT_MILLIS);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param userAgent The User-Agent string that should be used.
|
||||||
|
* @param contentTypePredicate An optional {@link Predicate}. If a content type is
|
||||||
|
* rejected by the predicate then a {@link HttpDataSource.InvalidContentTypeException} is
|
||||||
|
* thrown from {@link #open(DataSpec)}.
|
||||||
|
* @param listener An optional listener.
|
||||||
|
* @param connectTimeoutMillis The connection timeout, in milliseconds. A timeout of zero is
|
||||||
|
* interpreted as an infinite timeout.
|
||||||
|
* @param readTimeoutMillis The read timeout, in milliseconds. A timeout of zero is interpreted
|
||||||
|
* as an infinite timeout.
|
||||||
|
*/
|
||||||
|
public DefaultHttpDataSource(String userAgent, Predicate<String> contentTypePredicate,
|
||||||
|
TransferListener listener, int connectTimeoutMillis, int readTimeoutMillis) {
|
||||||
|
this.userAgent = Assertions.checkNotEmpty(userAgent);
|
||||||
|
this.contentTypePredicate = contentTypePredicate;
|
||||||
|
this.listener = listener;
|
||||||
|
this.requestProperties = new HashMap<String, String>();
|
||||||
|
this.connectTimeoutMillis = connectTimeoutMillis;
|
||||||
|
this.readTimeoutMillis = readTimeoutMillis;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getUrl() {
|
||||||
|
return connection == null ? null : connection.getURL().toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setRequestProperty(String name, String value) {
|
||||||
|
Assertions.checkNotNull(name);
|
||||||
|
Assertions.checkNotNull(value);
|
||||||
|
synchronized (requestProperties) {
|
||||||
|
requestProperties.put(name, value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void clearRequestProperty(String name) {
|
||||||
|
Assertions.checkNotNull(name);
|
||||||
|
synchronized (requestProperties) {
|
||||||
|
requestProperties.remove(name);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void clearAllRequestProperties() {
|
||||||
|
synchronized (requestProperties) {
|
||||||
|
requestProperties.clear();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* TODO: If the server uses gzip compression when serving the response, this may end up returning
|
||||||
|
* the size of the compressed response, where-as it should be returning the decompressed size or
|
||||||
|
* -1. See: developer.android.com/reference/java/net/HttpURLConnection.html
|
||||||
|
*
|
||||||
|
* To fix this we should:
|
||||||
|
*
|
||||||
|
* 1. Explicitly require no compression for media requests (since media should be compressed
|
||||||
|
* already) by setting the Accept-Encoding header to "identity"
|
||||||
|
* 2. In other cases, for example when requesting manifests, we don't want to disable compression.
|
||||||
|
* For these cases we should ensure that we return -1 here (and avoid performing any sanity
|
||||||
|
* checks on the content length).
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public long open(DataSpec dataSpec) throws HttpDataSourceException {
|
||||||
|
this.dataSpec = dataSpec;
|
||||||
|
this.bytesRead = 0;
|
||||||
|
try {
|
||||||
|
connection = makeConnection(dataSpec);
|
||||||
|
} catch (IOException e) {
|
||||||
|
throw new HttpDataSourceException("Unable to connect to " + dataSpec.uri.toString(), e,
|
||||||
|
dataSpec);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check for a valid response code.
|
||||||
|
int responseCode;
|
||||||
|
try {
|
||||||
|
responseCode = connection.getResponseCode();
|
||||||
|
} catch (IOException e) {
|
||||||
|
throw new HttpDataSourceException("Unable to connect to " + dataSpec.uri.toString(), e,
|
||||||
|
dataSpec);
|
||||||
|
}
|
||||||
|
if (responseCode < 200 || responseCode > 299) {
|
||||||
|
Map<String, List<String>> headers = connection.getHeaderFields();
|
||||||
|
closeConnection();
|
||||||
|
throw new InvalidResponseCodeException(responseCode, headers, dataSpec);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check for a valid content type.
|
||||||
|
String contentType = connection.getContentType();
|
||||||
|
if (contentTypePredicate != null && !contentTypePredicate.evaluate(contentType)) {
|
||||||
|
closeConnection();
|
||||||
|
throw new InvalidContentTypeException(contentType, dataSpec);
|
||||||
|
}
|
||||||
|
|
||||||
|
long contentLength = getContentLength(connection);
|
||||||
|
dataLength = dataSpec.length == C.LENGTH_UNBOUNDED ? contentLength : dataSpec.length;
|
||||||
|
|
||||||
|
if (dataSpec.length != C.LENGTH_UNBOUNDED && contentLength != C.LENGTH_UNBOUNDED
|
||||||
|
&& contentLength != dataSpec.length) {
|
||||||
|
// The DataSpec specified a length and we resolved a length from the response headers, but
|
||||||
|
// the two lengths do not match.
|
||||||
|
closeConnection();
|
||||||
|
throw new HttpDataSourceException(
|
||||||
|
new UnexpectedLengthException(dataSpec.length, contentLength), dataSpec);
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
inputStream = connection.getInputStream();
|
||||||
|
} catch (IOException e) {
|
||||||
|
closeConnection();
|
||||||
|
throw new HttpDataSourceException(e, dataSpec);
|
||||||
|
}
|
||||||
|
|
||||||
|
opened = true;
|
||||||
|
if (listener != null) {
|
||||||
|
listener.onTransferStart();
|
||||||
|
}
|
||||||
|
|
||||||
|
return dataLength;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int read(byte[] buffer, int offset, int readLength) throws HttpDataSourceException {
|
||||||
|
int read = 0;
|
||||||
|
try {
|
||||||
|
read = inputStream.read(buffer, offset, readLength);
|
||||||
|
} catch (IOException e) {
|
||||||
|
throw new HttpDataSourceException(e, dataSpec);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (read > 0) {
|
||||||
|
bytesRead += read;
|
||||||
|
if (listener != null) {
|
||||||
|
listener.onBytesTransferred(read);
|
||||||
|
}
|
||||||
|
} else if (dataLength != C.LENGTH_UNBOUNDED && dataLength != bytesRead) {
|
||||||
|
// Check for cases where the server closed the connection having not sent the correct amount
|
||||||
|
// of data. We can only do this if we know the length of the data we were expecting.
|
||||||
|
throw new HttpDataSourceException(new UnexpectedLengthException(dataLength, bytesRead),
|
||||||
|
dataSpec);
|
||||||
|
}
|
||||||
|
|
||||||
|
return read;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void close() throws HttpDataSourceException {
|
||||||
|
try {
|
||||||
|
if (inputStream != null) {
|
||||||
|
try {
|
||||||
|
inputStream.close();
|
||||||
|
} catch (IOException e) {
|
||||||
|
throw new HttpDataSourceException(e, dataSpec);
|
||||||
|
}
|
||||||
|
inputStream = null;
|
||||||
|
}
|
||||||
|
} finally {
|
||||||
|
if (opened) {
|
||||||
|
opened = false;
|
||||||
|
if (listener != null) {
|
||||||
|
listener.onTransferEnd();
|
||||||
|
}
|
||||||
|
closeConnection();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void closeConnection() {
|
||||||
|
if (connection != null) {
|
||||||
|
connection.disconnect();
|
||||||
|
connection = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the current connection, or null if the source is not currently opened.
|
||||||
|
*
|
||||||
|
* @return The current open connection, or null.
|
||||||
|
*/
|
||||||
|
protected final HttpURLConnection getConnection() {
|
||||||
|
return connection;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the number of bytes that have been read since the most recent call to
|
||||||
|
* {@link #open(DataSpec)}.
|
||||||
|
*
|
||||||
|
* @return The number of bytes read.
|
||||||
|
*/
|
||||||
|
protected final long bytesRead() {
|
||||||
|
return bytesRead;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the number of bytes that are still to be read for the current {@link DataSpec}.
|
||||||
|
* <p>
|
||||||
|
* If the total length of the data being read is known, then this length minus {@code bytesRead()}
|
||||||
|
* is returned. If the total length is unknown, {@link C#LENGTH_UNBOUNDED} is returned.
|
||||||
|
*
|
||||||
|
* @return The remaining length, or {@link C#LENGTH_UNBOUNDED}.
|
||||||
|
*/
|
||||||
|
protected final long bytesRemaining() {
|
||||||
|
return dataLength == C.LENGTH_UNBOUNDED ? dataLength : dataLength - bytesRead;
|
||||||
|
}
|
||||||
|
|
||||||
|
private HttpURLConnection makeConnection(DataSpec dataSpec) throws IOException {
|
||||||
|
URL url = new URL(dataSpec.uri.toString());
|
||||||
|
HttpURLConnection connection = (HttpURLConnection) url.openConnection();
|
||||||
|
connection.setConnectTimeout(connectTimeoutMillis);
|
||||||
|
connection.setReadTimeout(readTimeoutMillis);
|
||||||
|
connection.setDoOutput(false);
|
||||||
|
synchronized (requestProperties) {
|
||||||
|
for (Map.Entry<String, String> property : requestProperties.entrySet()) {
|
||||||
|
connection.setRequestProperty(property.getKey(), property.getValue());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
setRangeHeader(connection, dataSpec);
|
||||||
|
connection.setRequestProperty("User-Agent", userAgent);
|
||||||
|
connection.connect();
|
||||||
|
return connection;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void setRangeHeader(HttpURLConnection connection, DataSpec dataSpec) {
|
||||||
|
if (dataSpec.position == 0 && dataSpec.length == C.LENGTH_UNBOUNDED) {
|
||||||
|
// Not required.
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
String rangeRequest = "bytes=" + dataSpec.position + "-";
|
||||||
|
if (dataSpec.length != C.LENGTH_UNBOUNDED) {
|
||||||
|
rangeRequest += (dataSpec.position + dataSpec.length - 1);
|
||||||
|
}
|
||||||
|
connection.setRequestProperty("Range", rangeRequest);
|
||||||
|
}
|
||||||
|
|
||||||
|
private long getContentLength(HttpURLConnection connection) {
|
||||||
|
long contentLength = C.LENGTH_UNBOUNDED;
|
||||||
|
String contentLengthHeader = connection.getHeaderField("Content-Length");
|
||||||
|
if (!TextUtils.isEmpty(contentLengthHeader)) {
|
||||||
|
try {
|
||||||
|
contentLength = Long.parseLong(contentLengthHeader);
|
||||||
|
} catch (NumberFormatException e) {
|
||||||
|
Log.e(TAG, "Unexpected Content-Length [" + contentLengthHeader + "]");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
String contentRangeHeader = connection.getHeaderField("Content-Range");
|
||||||
|
if (!TextUtils.isEmpty(contentRangeHeader)) {
|
||||||
|
Matcher matcher = CONTENT_RANGE_HEADER.matcher(contentRangeHeader);
|
||||||
|
if (matcher.find()) {
|
||||||
|
try {
|
||||||
|
long contentLengthFromRange =
|
||||||
|
Long.parseLong(matcher.group(2)) - Long.parseLong(matcher.group(1)) + 1;
|
||||||
|
if (contentLength < 0) {
|
||||||
|
// Some proxy servers strip the Content-Length header. Fall back to the length
|
||||||
|
// calculated here in this case.
|
||||||
|
contentLength = contentLengthFromRange;
|
||||||
|
} else if (contentLength != contentLengthFromRange) {
|
||||||
|
// If there is a discrepancy between the Content-Length and Content-Range headers,
|
||||||
|
// assume the one with the larger value is correct. We have seen cases where carrier
|
||||||
|
// change one of them to reduce the size of a request, but it is unlikely anybody would
|
||||||
|
// increase it.
|
||||||
|
Log.w(TAG, "Inconsistent headers [" + contentLengthHeader + "] [" + contentRangeHeader
|
||||||
|
+ "]");
|
||||||
|
contentLength = Math.max(contentLength, contentLengthFromRange);
|
||||||
|
}
|
||||||
|
} catch (NumberFormatException e) {
|
||||||
|
Log.e(TAG, "Unexpected Content-Range [" + contentRangeHeader + "]");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return contentLength;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -15,28 +15,20 @@
|
|||||||
*/
|
*/
|
||||||
package com.google.android.exoplayer.upstream;
|
package com.google.android.exoplayer.upstream;
|
||||||
|
|
||||||
import com.google.android.exoplayer.C;
|
|
||||||
import com.google.android.exoplayer.util.Assertions;
|
|
||||||
import com.google.android.exoplayer.util.Predicate;
|
import com.google.android.exoplayer.util.Predicate;
|
||||||
import com.google.android.exoplayer.util.Util;
|
import com.google.android.exoplayer.util.Util;
|
||||||
|
|
||||||
import android.text.TextUtils;
|
import android.text.TextUtils;
|
||||||
import android.util.Log;
|
|
||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.io.InputStream;
|
|
||||||
import java.net.HttpURLConnection;
|
|
||||||
import java.net.URL;
|
|
||||||
import java.util.HashMap;
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import java.util.regex.Matcher;
|
|
||||||
import java.util.regex.Pattern;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* An http {@link DataSource}.
|
* An HTTP specific extension to {@link DataSource}.
|
||||||
*/
|
*/
|
||||||
public class HttpDataSource implements DataSource {
|
public interface HttpDataSource extends DataSource {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A {@link Predicate} that rejects content types often used for pay-walls.
|
* A {@link Predicate} that rejects content types often used for pay-walls.
|
||||||
@ -54,7 +46,7 @@ public class HttpDataSource implements DataSource {
|
|||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Thrown when an error is encountered when trying to read from HTTP data source.
|
* Thrown when an error is encountered when trying to read from a {@link HttpDataSource}.
|
||||||
*/
|
*/
|
||||||
public static class HttpDataSourceException extends IOException {
|
public static class HttpDataSourceException extends IOException {
|
||||||
|
|
||||||
@ -123,71 +115,14 @@ public class HttpDataSource implements DataSource {
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public static final int DEFAULT_CONNECT_TIMEOUT_MILLIS = 8 * 1000;
|
|
||||||
public static final int DEFAULT_READ_TIMEOUT_MILLIS = 8 * 1000;
|
|
||||||
|
|
||||||
private static final String TAG = "HttpDataSource";
|
|
||||||
private static final Pattern CONTENT_RANGE_HEADER =
|
|
||||||
Pattern.compile("^bytes (\\d+)-(\\d+)/(\\d+)$");
|
|
||||||
|
|
||||||
private final int connectTimeoutMillis;
|
|
||||||
private final int readTimeoutMillis;
|
|
||||||
private final String userAgent;
|
|
||||||
private final Predicate<String> contentTypePredicate;
|
|
||||||
private final HashMap<String, String> requestProperties;
|
|
||||||
private final TransferListener listener;
|
|
||||||
|
|
||||||
private DataSpec dataSpec;
|
|
||||||
private HttpURLConnection connection;
|
|
||||||
private InputStream inputStream;
|
|
||||||
private boolean opened;
|
|
||||||
|
|
||||||
private long dataLength;
|
|
||||||
private long bytesRead;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param userAgent The User-Agent string that should be used.
|
* When the source is open, returns the url from which data is being read.
|
||||||
* @param contentTypePredicate An optional {@link Predicate}. If a content type is
|
* <p>
|
||||||
* rejected by the predicate then a {@link InvalidContentTypeException} is thrown from
|
* If redirection occurred, the url after redirection is the one returned.
|
||||||
* {@link #open(DataSpec)}.
|
*
|
||||||
|
* @return When the source is open, the url from which data is being read. Null otherwise.
|
||||||
*/
|
*/
|
||||||
public HttpDataSource(String userAgent, Predicate<String> contentTypePredicate) {
|
String getUrl();
|
||||||
this(userAgent, contentTypePredicate, null);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @param userAgent The User-Agent string that should be used.
|
|
||||||
* @param contentTypePredicate An optional {@link Predicate}. If a content type is
|
|
||||||
* rejected by the predicate then a {@link InvalidContentTypeException} is thrown from
|
|
||||||
* {@link #open(DataSpec)}.
|
|
||||||
* @param listener An optional listener.
|
|
||||||
*/
|
|
||||||
public HttpDataSource(String userAgent, Predicate<String> contentTypePredicate,
|
|
||||||
TransferListener listener) {
|
|
||||||
this(userAgent, contentTypePredicate, listener, DEFAULT_CONNECT_TIMEOUT_MILLIS,
|
|
||||||
DEFAULT_READ_TIMEOUT_MILLIS);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @param userAgent The User-Agent string that should be used.
|
|
||||||
* @param contentTypePredicate An optional {@link Predicate}. If a content type is
|
|
||||||
* rejected by the predicate then a {@link InvalidContentTypeException} is thrown from
|
|
||||||
* {@link #open(DataSpec)}.
|
|
||||||
* @param listener An optional listener.
|
|
||||||
* @param connectTimeoutMillis The connection timeout, in milliseconds. A timeout of zero is
|
|
||||||
* interpreted as an infinite timeout.
|
|
||||||
* @param readTimeoutMillis The read timeout, in milliseconds. A timeout of zero is interpreted
|
|
||||||
* as an infinite timeout.
|
|
||||||
*/
|
|
||||||
public HttpDataSource(String userAgent, Predicate<String> contentTypePredicate,
|
|
||||||
TransferListener listener, int connectTimeoutMillis, int readTimeoutMillis) {
|
|
||||||
this.userAgent = Assertions.checkNotEmpty(userAgent);
|
|
||||||
this.contentTypePredicate = contentTypePredicate;
|
|
||||||
this.listener = listener;
|
|
||||||
this.requestProperties = new HashMap<String, String>();
|
|
||||||
this.connectTimeoutMillis = connectTimeoutMillis;
|
|
||||||
this.readTimeoutMillis = readTimeoutMillis;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Sets the value of a request header field. The value will be used for subsequent connections
|
* Sets the value of a request header field. The value will be used for subsequent connections
|
||||||
@ -196,13 +131,7 @@ public class HttpDataSource implements DataSource {
|
|||||||
* @param name The name of the header field.
|
* @param name The name of the header field.
|
||||||
* @param value The value of the field.
|
* @param value The value of the field.
|
||||||
*/
|
*/
|
||||||
public void setRequestProperty(String name, String value) {
|
void setRequestProperty(String name, String value);
|
||||||
Assertions.checkNotNull(name);
|
|
||||||
Assertions.checkNotNull(value);
|
|
||||||
synchronized (requestProperties) {
|
|
||||||
requestProperties.put(name, value);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Clears the value of a request header field. The change will apply to subsequent connections
|
* Clears the value of a request header field. The change will apply to subsequent connections
|
||||||
@ -210,243 +139,11 @@ public class HttpDataSource implements DataSource {
|
|||||||
*
|
*
|
||||||
* @param name The name of the header field.
|
* @param name The name of the header field.
|
||||||
*/
|
*/
|
||||||
public void clearRequestProperty(String name) {
|
void clearRequestProperty(String name);
|
||||||
Assertions.checkNotNull(name);
|
|
||||||
synchronized (requestProperties) {
|
|
||||||
requestProperties.remove(name);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Clears all request header fields that were set by {@link #setRequestProperty(String, String)}.
|
* Clears all request header fields that were set by {@link #setRequestProperty(String, String)}.
|
||||||
*/
|
*/
|
||||||
public void clearAllRequestProperties() {
|
void clearAllRequestProperties();
|
||||||
synchronized (requestProperties) {
|
|
||||||
requestProperties.clear();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
|
||||||
* TODO: If the server uses gzip compression when serving the response, this may end up returning
|
|
||||||
* the size of the compressed response, where-as it should be returning the decompressed size or
|
|
||||||
* -1. See: developer.android.com/reference/java/net/HttpURLConnection.html
|
|
||||||
*
|
|
||||||
* To fix this we should:
|
|
||||||
*
|
|
||||||
* 1. Explicitly require no compression for media requests (since media should be compressed
|
|
||||||
* already) by setting the Accept-Encoding header to "identity"
|
|
||||||
* 2. In other cases, for example when requesting manifests, we don't want to disable compression.
|
|
||||||
* For these cases we should ensure that we return -1 here (and avoid performing any sanity
|
|
||||||
* checks on the content length).
|
|
||||||
*/
|
|
||||||
@Override
|
|
||||||
public long open(DataSpec dataSpec) throws HttpDataSourceException {
|
|
||||||
this.dataSpec = dataSpec;
|
|
||||||
this.bytesRead = 0;
|
|
||||||
try {
|
|
||||||
connection = makeConnection(dataSpec);
|
|
||||||
} catch (IOException e) {
|
|
||||||
throw new HttpDataSourceException("Unable to connect to " + dataSpec.uri.toString(), e,
|
|
||||||
dataSpec);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Check for a valid response code.
|
|
||||||
int responseCode;
|
|
||||||
try {
|
|
||||||
responseCode = connection.getResponseCode();
|
|
||||||
} catch (IOException e) {
|
|
||||||
throw new HttpDataSourceException("Unable to connect to " + dataSpec.uri.toString(), e,
|
|
||||||
dataSpec);
|
|
||||||
}
|
|
||||||
if (responseCode < 200 || responseCode > 299) {
|
|
||||||
Map<String, List<String>> headers = connection.getHeaderFields();
|
|
||||||
closeConnection();
|
|
||||||
throw new InvalidResponseCodeException(responseCode, headers, dataSpec);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Check for a valid content type.
|
|
||||||
String contentType = connection.getContentType();
|
|
||||||
if (contentTypePredicate != null && !contentTypePredicate.evaluate(contentType)) {
|
|
||||||
closeConnection();
|
|
||||||
throw new InvalidContentTypeException(contentType, dataSpec);
|
|
||||||
}
|
|
||||||
|
|
||||||
long contentLength = getContentLength(connection);
|
|
||||||
dataLength = dataSpec.length == C.LENGTH_UNBOUNDED ? contentLength : dataSpec.length;
|
|
||||||
|
|
||||||
if (dataSpec.length != C.LENGTH_UNBOUNDED && contentLength != C.LENGTH_UNBOUNDED
|
|
||||||
&& contentLength != dataSpec.length) {
|
|
||||||
// The DataSpec specified a length and we resolved a length from the response headers, but
|
|
||||||
// the two lengths do not match.
|
|
||||||
closeConnection();
|
|
||||||
throw new HttpDataSourceException(
|
|
||||||
new UnexpectedLengthException(dataSpec.length, contentLength), dataSpec);
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
|
||||||
inputStream = connection.getInputStream();
|
|
||||||
} catch (IOException e) {
|
|
||||||
closeConnection();
|
|
||||||
throw new HttpDataSourceException(e, dataSpec);
|
|
||||||
}
|
|
||||||
|
|
||||||
opened = true;
|
|
||||||
if (listener != null) {
|
|
||||||
listener.onTransferStart();
|
|
||||||
}
|
|
||||||
|
|
||||||
return dataLength;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public int read(byte[] buffer, int offset, int readLength) throws HttpDataSourceException {
|
|
||||||
int read = 0;
|
|
||||||
try {
|
|
||||||
read = inputStream.read(buffer, offset, readLength);
|
|
||||||
} catch (IOException e) {
|
|
||||||
throw new HttpDataSourceException(e, dataSpec);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (read > 0) {
|
|
||||||
bytesRead += read;
|
|
||||||
if (listener != null) {
|
|
||||||
listener.onBytesTransferred(read);
|
|
||||||
}
|
|
||||||
} else if (dataLength != C.LENGTH_UNBOUNDED && dataLength != bytesRead) {
|
|
||||||
// Check for cases where the server closed the connection having not sent the correct amount
|
|
||||||
// of data. We can only do this if we know the length of the data we were expecting.
|
|
||||||
throw new HttpDataSourceException(new UnexpectedLengthException(dataLength, bytesRead),
|
|
||||||
dataSpec);
|
|
||||||
}
|
|
||||||
|
|
||||||
return read;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void close() throws HttpDataSourceException {
|
|
||||||
try {
|
|
||||||
if (inputStream != null) {
|
|
||||||
try {
|
|
||||||
inputStream.close();
|
|
||||||
} catch (IOException e) {
|
|
||||||
throw new HttpDataSourceException(e, dataSpec);
|
|
||||||
}
|
|
||||||
inputStream = null;
|
|
||||||
}
|
|
||||||
} finally {
|
|
||||||
if (opened) {
|
|
||||||
opened = false;
|
|
||||||
if (listener != null) {
|
|
||||||
listener.onTransferEnd();
|
|
||||||
}
|
|
||||||
closeConnection();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void closeConnection() {
|
|
||||||
if (connection != null) {
|
|
||||||
connection.disconnect();
|
|
||||||
connection = null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns the current connection, or null if the source is not currently opened.
|
|
||||||
*
|
|
||||||
* @return The current open connection, or null.
|
|
||||||
*/
|
|
||||||
protected final HttpURLConnection getConnection() {
|
|
||||||
return connection;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns the number of bytes that have been read since the most recent call to
|
|
||||||
* {@link #open(DataSpec)}.
|
|
||||||
*
|
|
||||||
* @return The number of bytes read.
|
|
||||||
*/
|
|
||||||
protected final long bytesRead() {
|
|
||||||
return bytesRead;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns the number of bytes that are still to be read for the current {@link DataSpec}.
|
|
||||||
* <p>
|
|
||||||
* If the total length of the data being read is known, then this length minus {@code bytesRead()}
|
|
||||||
* is returned. If the total length is unknown, {@link C#LENGTH_UNBOUNDED} is returned.
|
|
||||||
*
|
|
||||||
* @return The remaining length, or {@link C#LENGTH_UNBOUNDED}.
|
|
||||||
*/
|
|
||||||
protected final long bytesRemaining() {
|
|
||||||
return dataLength == C.LENGTH_UNBOUNDED ? dataLength : dataLength - bytesRead;
|
|
||||||
}
|
|
||||||
|
|
||||||
private HttpURLConnection makeConnection(DataSpec dataSpec) throws IOException {
|
|
||||||
URL url = new URL(dataSpec.uri.toString());
|
|
||||||
HttpURLConnection connection = (HttpURLConnection) url.openConnection();
|
|
||||||
connection.setConnectTimeout(connectTimeoutMillis);
|
|
||||||
connection.setReadTimeout(readTimeoutMillis);
|
|
||||||
connection.setDoOutput(false);
|
|
||||||
synchronized (requestProperties) {
|
|
||||||
for (Map.Entry<String, String> property : requestProperties.entrySet()) {
|
|
||||||
connection.setRequestProperty(property.getKey(), property.getValue());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
setRangeHeader(connection, dataSpec);
|
|
||||||
connection.setRequestProperty("User-Agent", userAgent);
|
|
||||||
connection.connect();
|
|
||||||
return connection;
|
|
||||||
}
|
|
||||||
|
|
||||||
private void setRangeHeader(HttpURLConnection connection, DataSpec dataSpec) {
|
|
||||||
if (dataSpec.position == 0 && dataSpec.length == C.LENGTH_UNBOUNDED) {
|
|
||||||
// Not required.
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
String rangeRequest = "bytes=" + dataSpec.position + "-";
|
|
||||||
if (dataSpec.length != C.LENGTH_UNBOUNDED) {
|
|
||||||
rangeRequest += (dataSpec.position + dataSpec.length - 1);
|
|
||||||
}
|
|
||||||
connection.setRequestProperty("Range", rangeRequest);
|
|
||||||
}
|
|
||||||
|
|
||||||
private long getContentLength(HttpURLConnection connection) {
|
|
||||||
long contentLength = C.LENGTH_UNBOUNDED;
|
|
||||||
String contentLengthHeader = connection.getHeaderField("Content-Length");
|
|
||||||
if (!TextUtils.isEmpty(contentLengthHeader)) {
|
|
||||||
try {
|
|
||||||
contentLength = Long.parseLong(contentLengthHeader);
|
|
||||||
} catch (NumberFormatException e) {
|
|
||||||
Log.e(TAG, "Unexpected Content-Length [" + contentLengthHeader + "]");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
String contentRangeHeader = connection.getHeaderField("Content-Range");
|
|
||||||
if (!TextUtils.isEmpty(contentRangeHeader)) {
|
|
||||||
Matcher matcher = CONTENT_RANGE_HEADER.matcher(contentRangeHeader);
|
|
||||||
if (matcher.find()) {
|
|
||||||
try {
|
|
||||||
long contentLengthFromRange =
|
|
||||||
Long.parseLong(matcher.group(2)) - Long.parseLong(matcher.group(1)) + 1;
|
|
||||||
if (contentLength < 0) {
|
|
||||||
// Some proxy servers strip the Content-Length header. Fall back to the length
|
|
||||||
// calculated here in this case.
|
|
||||||
contentLength = contentLengthFromRange;
|
|
||||||
} else if (contentLength != contentLengthFromRange) {
|
|
||||||
// If there is a discrepancy between the Content-Length and Content-Range headers,
|
|
||||||
// assume the one with the larger value is correct. We have seen cases where carrier
|
|
||||||
// change one of them to reduce the size of a request, but it is unlikely anybody would
|
|
||||||
// increase it.
|
|
||||||
Log.w(TAG, "Inconsistent headers [" + contentLengthHeader + "] [" + contentRangeHeader +
|
|
||||||
"]");
|
|
||||||
contentLength = Math.max(contentLength, contentLengthFromRange);
|
|
||||||
}
|
|
||||||
} catch (NumberFormatException e) {
|
|
||||||
Log.e(TAG, "Unexpected Content-Range [" + contentRangeHeader + "]");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return contentLength;
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -13,15 +13,15 @@
|
|||||||
* 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.
|
||||||
@ -40,21 +40,16 @@ public final class NetworkLoadable<T> implements Loadable {
|
|||||||
*
|
*
|
||||||
* @param connectionUrl The source of the response, after any redirection.
|
* @param connectionUrl The source of the response, after any redirection.
|
||||||
* @param inputStream An {@link InputStream} from which the response data can be read.
|
* @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.
|
* @return The parsed object.
|
||||||
* @throws ParserException If an error occurs parsing the data.
|
* @throws ParserException If an error occurs parsing the data.
|
||||||
* @throws IOException If an error occurs reading data from the stream.
|
* @throws IOException If an error occurs reading data from the stream.
|
||||||
*/
|
*/
|
||||||
T parse(String connectionUrl, InputStream inputStream, String inputEncoding)
|
T parse(String connectionUrl, InputStream inputStream) throws ParserException, IOException;
|
||||||
throws ParserException, IOException;
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public static final int DEFAULT_TIMEOUT_MILLIS = 10000;
|
private final DataSpec dataSpec;
|
||||||
|
private final HttpDataSource httpDataSource;
|
||||||
private final String url;
|
|
||||||
private final String userAgent;
|
|
||||||
private final int timeoutMillis;
|
|
||||||
private final Parser<T> parser;
|
private final Parser<T> parser;
|
||||||
|
|
||||||
private volatile T result;
|
private volatile T result;
|
||||||
@ -62,24 +57,13 @@ public final class NetworkLoadable<T> implements Loadable {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* @param url The url from which the object should be loaded.
|
* @param url The url from which the object should be loaded.
|
||||||
* @param userAgent The user agent to use when requesting the object.
|
* @param httpDataSource A {@link HttpDataSource} to use when loading the data.
|
||||||
* @param parser Parses the object from the network response.
|
* @param parser Parses the object from the network response.
|
||||||
*/
|
*/
|
||||||
public NetworkLoadable(String url, String userAgent, Parser<T> parser) {
|
public NetworkLoadable(String url, HttpDataSource httpDataSource, Parser<T> parser) {
|
||||||
this(url, userAgent, DEFAULT_TIMEOUT_MILLIS, parser);
|
this.httpDataSource = httpDataSource;
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @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, Parser<T> parser) {
|
|
||||||
this.url = url;
|
|
||||||
this.userAgent = userAgent;
|
|
||||||
this.timeoutMillis = timeoutMillis;
|
|
||||||
this.parser = parser;
|
this.parser = parser;
|
||||||
|
dataSpec = new DataSpec(Uri.parse(url));
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -103,28 +87,13 @@ public final 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 = parser.parse(connection.getURL().toString(), inputStream, inputEncoding);
|
|
||||||
} finally {
|
} finally {
|
||||||
if (inputStream != null) {
|
inputStream.close();
|
||||||
inputStream.close();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
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 {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* 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,8 +15,10 @@
|
|||||||
*/
|
*/
|
||||||
package com.google.android.exoplayer.util;
|
package com.google.android.exoplayer.util;
|
||||||
|
|
||||||
|
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;
|
||||||
@ -28,6 +30,18 @@ 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.
|
||||||
*/
|
*/
|
||||||
@ -70,7 +84,7 @@ public class ManifestFetcher<T> implements Loader.Callback {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private final NetworkLoadable.Parser<T> parser;
|
private final NetworkLoadable.Parser<T> parser;
|
||||||
private final String userAgent;
|
private final HttpDataSource httpDataSource;
|
||||||
private final Handler eventHandler;
|
private final Handler eventHandler;
|
||||||
private final EventListener eventListener;
|
private final EventListener eventListener;
|
||||||
|
|
||||||
@ -89,26 +103,27 @@ public class ManifestFetcher<T> implements Loader.Callback {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* @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 parser A parser to parse the loaded manifest data.
|
||||||
*/
|
*/
|
||||||
public ManifestFetcher(String manifestUrl, String userAgent, NetworkLoadable.Parser<T> parser) {
|
public ManifestFetcher(String manifestUrl, HttpDataSource httpDataSource,
|
||||||
this(manifestUrl, userAgent, parser, null, null);
|
NetworkLoadable.Parser<T> parser) {
|
||||||
|
this(manifestUrl, httpDataSource, parser, null, 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 parser A parser to parse the loaded manifest data.
|
||||||
* @param eventHandler A handler to use when delivering events to {@code eventListener}. May be
|
* @param eventHandler A handler to use when delivering events to {@code eventListener}. May be
|
||||||
* null if delivery of events is not required.
|
* null if delivery of events is not required.
|
||||||
* @param eventListener A listener of events. 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(String manifestUrl, String userAgent, NetworkLoadable.Parser<T> parser,
|
public ManifestFetcher(String manifestUrl, HttpDataSource httpDataSource,
|
||||||
Handler eventHandler, EventListener eventListener) {
|
NetworkLoadable.Parser<T> parser, Handler eventHandler, EventListener eventListener) {
|
||||||
this.parser = parser;
|
this.parser = parser;
|
||||||
this.manifestUrl = manifestUrl;
|
this.manifestUrl = manifestUrl;
|
||||||
this.userAgent = userAgent;
|
this.httpDataSource = httpDataSource;
|
||||||
this.eventHandler = eventHandler;
|
this.eventHandler = eventHandler;
|
||||||
this.eventListener = eventListener;
|
this.eventListener = eventListener;
|
||||||
}
|
}
|
||||||
@ -131,7 +146,7 @@ public class ManifestFetcher<T> implements Loader.Callback {
|
|||||||
*/
|
*/
|
||||||
public void singleLoad(Looper callbackLooper, final ManifestCallback<T> callback) {
|
public void singleLoad(Looper callbackLooper, final ManifestCallback<T> callback) {
|
||||||
SingleFetchHelper fetchHelper = new SingleFetchHelper(
|
SingleFetchHelper fetchHelper = new SingleFetchHelper(
|
||||||
new NetworkLoadable<T>(manifestUrl, userAgent, parser), callbackLooper, callback);
|
new NetworkLoadable<T>(manifestUrl, httpDataSource, parser), callbackLooper, callback);
|
||||||
fetchHelper.startLoading();
|
fetchHelper.startLoading();
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -204,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 NetworkLoadable<T>(manifestUrl, userAgent, parser);
|
currentLoadable = new NetworkLoadable<T>(manifestUrl, httpDataSource, parser);
|
||||||
loader.startLoading(currentLoadable, this);
|
loader.startLoading(currentLoadable, this);
|
||||||
notifyManifestRefreshStarted();
|
notifyManifestRefreshStarted();
|
||||||
}
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user