Commit 462fea3e by Oliver Woodman

Correctly resolve Uris according to RFC3986.

Issue: #327
parent 457557b5
...@@ -581,7 +581,7 @@ public class DashChunkSource implements ChunkSource { ...@@ -581,7 +581,7 @@ public class DashChunkSource implements ChunkSource {
} }
if ((result & Extractor.RESULT_READ_INDEX) != 0) { if ((result & Extractor.RESULT_READ_INDEX) != 0) {
representationHolders.get(format.id).segmentIndex = representationHolders.get(format.id).segmentIndex =
new DashWrappingSegmentIndex(extractor.getIndex(), uri, indexAnchor); new DashWrappingSegmentIndex(extractor.getIndex(), uri.toString(), indexAnchor);
} }
} }
......
...@@ -19,8 +19,6 @@ import com.google.android.exoplayer.chunk.parser.SegmentIndex; ...@@ -19,8 +19,6 @@ import com.google.android.exoplayer.chunk.parser.SegmentIndex;
import com.google.android.exoplayer.dash.mpd.RangedUri; import com.google.android.exoplayer.dash.mpd.RangedUri;
import com.google.android.exoplayer.util.Util; import com.google.android.exoplayer.util.Util;
import android.net.Uri;
/** /**
* An implementation of {@link DashSegmentIndex} that wraps a {@link SegmentIndex} parsed from a * An implementation of {@link DashSegmentIndex} that wraps a {@link SegmentIndex} parsed from a
* media stream. * media stream.
...@@ -28,16 +26,16 @@ import android.net.Uri; ...@@ -28,16 +26,16 @@ import android.net.Uri;
public class DashWrappingSegmentIndex implements DashSegmentIndex { public class DashWrappingSegmentIndex implements DashSegmentIndex {
private final SegmentIndex segmentIndex; private final SegmentIndex segmentIndex;
private final Uri uri; private final String uri;
private final long indexAnchor; private final long indexAnchor;
/** /**
* @param segmentIndex The {@link SegmentIndex} to wrap. * @param segmentIndex The {@link SegmentIndex} to wrap.
* @param uri The {@link Uri} where the data is located. * @param uri The URI where the data is located.
* @param indexAnchor The index anchor point. This value is added to the byte offsets specified * @param indexAnchor The index anchor point. This value is added to the byte offsets specified
* in the wrapped {@link SegmentIndex}. * in the wrapped {@link SegmentIndex}.
*/ */
public DashWrappingSegmentIndex(SegmentIndex segmentIndex, Uri uri, long indexAnchor) { public DashWrappingSegmentIndex(SegmentIndex segmentIndex, String uri, long indexAnchor) {
this.segmentIndex = segmentIndex; this.segmentIndex = segmentIndex;
this.uri = uri; this.uri = uri;
this.indexAnchor = indexAnchor; this.indexAnchor = indexAnchor;
......
...@@ -24,9 +24,9 @@ import com.google.android.exoplayer.dash.mpd.SegmentBase.SingleSegmentBase; ...@@ -24,9 +24,9 @@ import com.google.android.exoplayer.dash.mpd.SegmentBase.SingleSegmentBase;
import com.google.android.exoplayer.upstream.NetworkLoadable; 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.UriUtil;
import com.google.android.exoplayer.util.Util; import com.google.android.exoplayer.util.Util;
import android.net.Uri;
import android.text.TextUtils; import android.text.TextUtils;
import org.xml.sax.helpers.DefaultHandler; import org.xml.sax.helpers.DefaultHandler;
...@@ -83,7 +83,7 @@ public class MediaPresentationDescriptionParser extends DefaultHandler ...@@ -83,7 +83,7 @@ public class MediaPresentationDescriptionParser extends DefaultHandler
throw new ParserException( throw new ParserException(
"inputStream does not contain a valid media presentation description"); "inputStream does not contain a valid media presentation description");
} }
return parseMediaPresentationDescription(xpp, Util.parseBaseUri(connectionUrl)); return parseMediaPresentationDescription(xpp, connectionUrl);
} catch (XmlPullParserException e) { } catch (XmlPullParserException e) {
throw new ParserException(e); throw new ParserException(e);
} catch (ParseException e) { } catch (ParseException e) {
...@@ -92,7 +92,7 @@ public class MediaPresentationDescriptionParser extends DefaultHandler ...@@ -92,7 +92,7 @@ public class MediaPresentationDescriptionParser extends DefaultHandler
} }
protected MediaPresentationDescription parseMediaPresentationDescription(XmlPullParser xpp, protected MediaPresentationDescription parseMediaPresentationDescription(XmlPullParser xpp,
Uri baseUrl) throws XmlPullParserException, IOException, ParseException { String 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);
...@@ -137,7 +137,7 @@ public class MediaPresentationDescriptionParser extends DefaultHandler ...@@ -137,7 +137,7 @@ public class MediaPresentationDescriptionParser extends DefaultHandler
return new UtcTimingElement(schemeIdUri, value); return new UtcTimingElement(schemeIdUri, value);
} }
protected Period parsePeriod(XmlPullParser xpp, Uri baseUrl, long mpdDurationMs) protected Period parsePeriod(XmlPullParser xpp, String 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);
...@@ -170,7 +170,7 @@ public class MediaPresentationDescriptionParser extends DefaultHandler ...@@ -170,7 +170,7 @@ public class MediaPresentationDescriptionParser extends DefaultHandler
// AdaptationSet parsing. // AdaptationSet parsing.
protected AdaptationSet parseAdaptationSet(XmlPullParser xpp, Uri baseUrl, long periodStartMs, protected AdaptationSet parseAdaptationSet(XmlPullParser xpp, String baseUrl, long periodStartMs,
long periodDurationMs, SegmentBase segmentBase) throws XmlPullParserException, IOException { long periodDurationMs, SegmentBase segmentBase) throws XmlPullParserException, IOException {
String mimeType = xpp.getAttributeValue(null, "mimeType"); String mimeType = xpp.getAttributeValue(null, "mimeType");
...@@ -287,9 +287,9 @@ public class MediaPresentationDescriptionParser extends DefaultHandler ...@@ -287,9 +287,9 @@ public class MediaPresentationDescriptionParser extends DefaultHandler
// Representation parsing. // Representation parsing.
protected Representation parseRepresentation(XmlPullParser xpp, Uri baseUrl, long periodStartMs, protected Representation parseRepresentation(XmlPullParser xpp, String baseUrl,
long periodDurationMs, String mimeType, String language, SegmentBase segmentBase) long periodStartMs, long periodDurationMs, String mimeType, String language,
throws XmlPullParserException, IOException { SegmentBase segmentBase) 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");
...@@ -335,7 +335,7 @@ public class MediaPresentationDescriptionParser extends DefaultHandler ...@@ -335,7 +335,7 @@ public class MediaPresentationDescriptionParser extends DefaultHandler
// SegmentBase, SegmentList and SegmentTemplate parsing. // SegmentBase, SegmentList and SegmentTemplate parsing.
protected SingleSegmentBase parseSegmentBase(XmlPullParser xpp, Uri baseUrl, protected SingleSegmentBase parseSegmentBase(XmlPullParser xpp, String baseUrl,
SingleSegmentBase parent) throws XmlPullParserException, IOException { SingleSegmentBase parent) throws XmlPullParserException, IOException {
long timescale = parseLong(xpp, "timescale", parent != null ? parent.timescale : 1); long timescale = parseLong(xpp, "timescale", parent != null ? parent.timescale : 1);
...@@ -364,12 +364,12 @@ public class MediaPresentationDescriptionParser extends DefaultHandler ...@@ -364,12 +364,12 @@ public class MediaPresentationDescriptionParser extends DefaultHandler
} }
protected SingleSegmentBase buildSingleSegmentBase(RangedUri initialization, long timescale, protected SingleSegmentBase buildSingleSegmentBase(RangedUri initialization, long timescale,
long presentationTimeOffset, Uri baseUrl, long indexStart, long indexLength) { long presentationTimeOffset, String baseUrl, long indexStart, long indexLength) {
return new SingleSegmentBase(initialization, timescale, presentationTimeOffset, baseUrl, return new SingleSegmentBase(initialization, timescale, presentationTimeOffset, baseUrl,
indexStart, indexLength); indexStart, indexLength);
} }
protected SegmentList parseSegmentList(XmlPullParser xpp, Uri baseUrl, SegmentList parent, protected SegmentList parseSegmentList(XmlPullParser xpp, String baseUrl, SegmentList parent,
long periodDurationMs) throws XmlPullParserException, IOException { long periodDurationMs) throws XmlPullParserException, IOException {
long timescale = parseLong(xpp, "timescale", parent != null ? parent.timescale : 1); long timescale = parseLong(xpp, "timescale", parent != null ? parent.timescale : 1);
...@@ -413,7 +413,7 @@ public class MediaPresentationDescriptionParser extends DefaultHandler ...@@ -413,7 +413,7 @@ public class MediaPresentationDescriptionParser extends DefaultHandler
startNumber, duration, timeline, segments); startNumber, duration, timeline, segments);
} }
protected SegmentTemplate parseSegmentTemplate(XmlPullParser xpp, Uri baseUrl, protected SegmentTemplate parseSegmentTemplate(XmlPullParser xpp, String baseUrl,
SegmentTemplate parent, long periodDurationMs) throws XmlPullParserException, IOException { SegmentTemplate parent, long periodDurationMs) throws XmlPullParserException, IOException {
long timescale = parseLong(xpp, "timescale", parent != null ? parent.timescale : 1); long timescale = parseLong(xpp, "timescale", parent != null ? parent.timescale : 1);
...@@ -450,7 +450,7 @@ public class MediaPresentationDescriptionParser extends DefaultHandler ...@@ -450,7 +450,7 @@ public class MediaPresentationDescriptionParser extends DefaultHandler
protected SegmentTemplate buildSegmentTemplate(RangedUri initialization, long timescale, protected SegmentTemplate buildSegmentTemplate(RangedUri initialization, long timescale,
long presentationTimeOffset, long periodDurationMs, int startNumber, long duration, long presentationTimeOffset, long periodDurationMs, int startNumber, long duration,
List<SegmentTimelineElement> timeline, UrlTemplate initializationTemplate, List<SegmentTimelineElement> timeline, UrlTemplate initializationTemplate,
UrlTemplate mediaTemplate, Uri baseUrl) { UrlTemplate mediaTemplate, String baseUrl) {
return new SegmentTemplate(initialization, timescale, presentationTimeOffset, periodDurationMs, return new SegmentTemplate(initialization, timescale, presentationTimeOffset, periodDurationMs,
startNumber, duration, timeline, initializationTemplate, mediaTemplate, baseUrl); startNumber, duration, timeline, initializationTemplate, mediaTemplate, baseUrl);
} }
...@@ -487,15 +487,15 @@ public class MediaPresentationDescriptionParser extends DefaultHandler ...@@ -487,15 +487,15 @@ public class MediaPresentationDescriptionParser extends DefaultHandler
return defaultValue; return defaultValue;
} }
protected RangedUri parseInitialization(XmlPullParser xpp, Uri baseUrl) { protected RangedUri parseInitialization(XmlPullParser xpp, String baseUrl) {
return parseRangedUrl(xpp, baseUrl, "sourceURL", "range"); return parseRangedUrl(xpp, baseUrl, "sourceURL", "range");
} }
protected RangedUri parseSegmentUrl(XmlPullParser xpp, Uri baseUrl) { protected RangedUri parseSegmentUrl(XmlPullParser xpp, String baseUrl) {
return parseRangedUrl(xpp, baseUrl, "media", "mediaRange"); return parseRangedUrl(xpp, baseUrl, "media", "mediaRange");
} }
protected RangedUri parseRangedUrl(XmlPullParser xpp, Uri baseUrl, String urlAttribute, protected RangedUri parseRangedUrl(XmlPullParser xpp, String baseUrl, String urlAttribute,
String rangeAttribute) { String rangeAttribute) {
String urlText = xpp.getAttributeValue(null, urlAttribute); String urlText = xpp.getAttributeValue(null, urlAttribute);
long rangeStart = 0; long rangeStart = 0;
...@@ -509,7 +509,7 @@ public class MediaPresentationDescriptionParser extends DefaultHandler ...@@ -509,7 +509,7 @@ public class MediaPresentationDescriptionParser extends DefaultHandler
return buildRangedUri(baseUrl, urlText, rangeStart, rangeLength); return buildRangedUri(baseUrl, urlText, rangeStart, rangeLength);
} }
protected RangedUri buildRangedUri(Uri baseUrl, String urlText, long rangeStart, protected RangedUri buildRangedUri(String baseUrl, String urlText, long rangeStart,
long rangeLength) { long rangeLength) {
return new RangedUri(baseUrl, urlText, rangeStart, rangeLength); return new RangedUri(baseUrl, urlText, rangeStart, rangeLength);
} }
...@@ -548,15 +548,10 @@ public class MediaPresentationDescriptionParser extends DefaultHandler ...@@ -548,15 +548,10 @@ public class MediaPresentationDescriptionParser extends DefaultHandler
} }
} }
protected static Uri parseBaseUrl(XmlPullParser xpp, Uri parentBaseUrl) protected static String parseBaseUrl(XmlPullParser xpp, String parentBaseUrl)
throws XmlPullParserException, IOException { throws XmlPullParserException, IOException {
xpp.next(); xpp.next();
String newBaseUrlText = xpp.getText(); return UriUtil.resolve(parentBaseUrl, xpp.getText());
Uri newBaseUri = Uri.parse(newBaseUrlText);
if (!newBaseUri.isAbsolute()) {
newBaseUri = Uri.withAppendedPath(parentBaseUrl, newBaseUrlText);
}
return newBaseUri;
} }
protected static int parseInt(XmlPullParser xpp, String name) { protected static int parseInt(XmlPullParser xpp, String name) {
......
...@@ -16,7 +16,7 @@ ...@@ -16,7 +16,7 @@
package com.google.android.exoplayer.dash.mpd; package com.google.android.exoplayer.dash.mpd;
import com.google.android.exoplayer.util.Assertions; import com.google.android.exoplayer.util.Assertions;
import com.google.android.exoplayer.util.Util; import com.google.android.exoplayer.util.UriUtil;
import android.net.Uri; import android.net.Uri;
...@@ -35,31 +35,28 @@ public final class RangedUri { ...@@ -35,31 +35,28 @@ public final class RangedUri {
*/ */
public final long length; public final long length;
// The {@link Uri} is stored internally in two parts, {@link #baseUri} and {@link uriString}. // The URI is stored internally in two parts: reference URI and a base URI to use when
// This helps optimize memory usage in the same way that DASH manifests allow many URLs to be // resolving it. This helps optimize memory usage in the same way that DASH manifests allow many
// expressed concisely in the form of a single BaseURL and many relative paths. Note that this // URLs to be expressed concisely in the form of a single BaseURL and many relative paths. Note
// optimization relies on the same {@code Uri} being passed as the {@link #baseUri} to many // that this optimization relies on the same object being passed as the base URI to many
// instances of this class. // instances of this class.
private final Uri baseUri; private final String baseUri;
private final String stringUri; private final String referenceUri;
private int hashCode; private int hashCode;
/** /**
* Constructs an ranged uri. * Constructs an ranged uri.
* <p>
* See {@link Util#getMergedUri(Uri, String)} for a description of how {@code baseUri} and
* {@code stringUri} are merged.
* *
* @param baseUri A uri that can form the base of the uri defined by the instance. * @param baseUri A uri that can form the base of the uri defined by the instance.
* @param stringUri A relative or absolute uri in string form. * @param referenceUri A reference uri that should be resolved with respect to {@code baseUri}.
* @param start The (zero based) index of the first byte of the range. * @param start The (zero based) index of the first byte of the range.
* @param length The length of the range, or -1 to indicate that the range is unbounded. * @param length The length of the range, or -1 to indicate that the range is unbounded.
*/ */
public RangedUri(Uri baseUri, String stringUri, long start, long length) { public RangedUri(String baseUri, String referenceUri, long start, long length) {
Assertions.checkArgument(baseUri != null || stringUri != null); Assertions.checkArgument(baseUri != null || referenceUri != null);
this.baseUri = baseUri; this.baseUri = baseUri;
this.stringUri = stringUri; this.referenceUri = referenceUri;
this.start = start; this.start = start;
this.length = length; this.length = length;
} }
...@@ -70,7 +67,16 @@ public final class RangedUri { ...@@ -70,7 +67,16 @@ public final class RangedUri {
* @return The {@link Uri} represented by the instance. * @return The {@link Uri} represented by the instance.
*/ */
public Uri getUri() { public Uri getUri() {
return Util.getMergedUri(baseUri, stringUri); return UriUtil.resolveToUri(baseUri, referenceUri);
}
/**
* Returns the uri represented by the instance as a string.
*
* @return The uri represented by the instance.
*/
public String getUriString() {
return UriUtil.resolve(baseUri, referenceUri);
} }
/** /**
...@@ -85,13 +91,13 @@ public final class RangedUri { ...@@ -85,13 +91,13 @@ public final class RangedUri {
* @return The merged {@link RangedUri} if the merge was successful. Null otherwise. * @return The merged {@link RangedUri} if the merge was successful. Null otherwise.
*/ */
public RangedUri attemptMerge(RangedUri other) { public RangedUri attemptMerge(RangedUri other) {
if (other == null || !getUri().equals(other.getUri())) { if (other == null || !getUriString().equals(other.getUriString())) {
return null; return null;
} else if (length != -1 && start + length == other.start) { } else if (length != -1 && start + length == other.start) {
return new RangedUri(baseUri, stringUri, start, return new RangedUri(baseUri, referenceUri, start,
other.length == -1 ? -1 : length + other.length); other.length == -1 ? -1 : length + other.length);
} else if (other.length != -1 && other.start + other.length == start) { } else if (other.length != -1 && other.start + other.length == start) {
return new RangedUri(baseUri, stringUri, other.start, return new RangedUri(baseUri, referenceUri, other.start,
length == -1 ? -1 : other.length + length); length == -1 ? -1 : other.length + length);
} else { } else {
return null; return null;
...@@ -104,7 +110,7 @@ public final class RangedUri { ...@@ -104,7 +110,7 @@ public final class RangedUri {
int result = 17; int result = 17;
result = 31 * result + (int) start; result = 31 * result + (int) start;
result = 31 * result + (int) length; result = 31 * result + (int) length;
result = 31 * result + getUri().hashCode(); result = 31 * result + getUriString().hashCode();
hashCode = result; hashCode = result;
} }
return hashCode; return hashCode;
...@@ -121,7 +127,7 @@ public final class RangedUri { ...@@ -121,7 +127,7 @@ public final class RangedUri {
RangedUri other = (RangedUri) obj; RangedUri other = (RangedUri) obj;
return this.start == other.start return this.start == other.start
&& this.length == other.length && this.length == other.length
&& getUri().equals(other.getUri()); && getUriString().equals(other.getUriString());
} }
} }
...@@ -147,7 +147,7 @@ public abstract class Representation { ...@@ -147,7 +147,7 @@ public abstract class Representation {
public static class SingleSegmentRepresentation extends Representation { public static class SingleSegmentRepresentation extends Representation {
/** /**
* The {@link Uri} of the single segment. * The uri of the single segment.
*/ */
public final Uri uri; public final Uri uri;
...@@ -174,7 +174,7 @@ public abstract class Representation { ...@@ -174,7 +174,7 @@ public abstract class Representation {
* @param contentLength The content length, or -1 if unknown. * @param contentLength The content length, or -1 if unknown.
*/ */
public static SingleSegmentRepresentation newInstance(long periodStartMs, long periodDurationMs, public static SingleSegmentRepresentation newInstance(long periodStartMs, long periodDurationMs,
String contentId, long revisionId, Format format, Uri uri, long initializationStart, String contentId, long revisionId, Format format, String uri, long initializationStart,
long initializationEnd, long indexStart, long indexEnd, long contentLength) { long initializationEnd, long indexStart, long indexEnd, long contentLength) {
RangedUri rangedUri = new RangedUri(uri, null, initializationStart, RangedUri rangedUri = new RangedUri(uri, null, initializationStart,
initializationEnd - initializationStart + 1); initializationEnd - initializationStart + 1);
...@@ -197,13 +197,13 @@ public abstract class Representation { ...@@ -197,13 +197,13 @@ public abstract class Representation {
public SingleSegmentRepresentation(long periodStartMs, long periodDurationMs, String contentId, public SingleSegmentRepresentation(long periodStartMs, long periodDurationMs, String contentId,
long revisionId, Format format, SingleSegmentBase segmentBase, long contentLength) { long revisionId, Format format, SingleSegmentBase segmentBase, long contentLength) {
super(periodStartMs, periodDurationMs, contentId, revisionId, format, segmentBase); super(periodStartMs, periodDurationMs, contentId, revisionId, format, segmentBase);
this.uri = segmentBase.uri; this.uri = Uri.parse(segmentBase.uri);
this.indexUri = segmentBase.getIndex(); this.indexUri = segmentBase.getIndex();
this.contentLength = contentLength; this.contentLength = contentLength;
// If we have an index uri then the index is defined externally, and we shouldn't return one // If we have an index uri then the index is defined externally, and we shouldn't return one
// directly. If we don't, then we can't do better than an index defining a single segment. // directly. If we don't, then we can't do better than an index defining a single segment.
segmentIndex = indexUri != null ? null : new DashSingleSegmentIndex(periodStartMs * 1000, segmentIndex = indexUri != null ? null : new DashSingleSegmentIndex(periodStartMs * 1000,
periodDurationMs * 1000, new RangedUri(uri, null, 0, -1)); periodDurationMs * 1000, new RangedUri(segmentBase.uri, null, 0, -1));
} }
@Override @Override
......
...@@ -19,8 +19,6 @@ import com.google.android.exoplayer.C; ...@@ -19,8 +19,6 @@ import com.google.android.exoplayer.C;
import com.google.android.exoplayer.dash.DashSegmentIndex; import com.google.android.exoplayer.dash.DashSegmentIndex;
import com.google.android.exoplayer.util.Util; import com.google.android.exoplayer.util.Util;
import android.net.Uri;
import java.util.List; import java.util.List;
/** /**
...@@ -73,7 +71,7 @@ public abstract class SegmentBase { ...@@ -73,7 +71,7 @@ public abstract class SegmentBase {
/** /**
* The uri of the segment. * The uri of the segment.
*/ */
public final Uri uri; public final String uri;
/* package */ final long indexStart; /* package */ final long indexStart;
/* package */ final long indexLength; /* package */ final long indexLength;
...@@ -89,7 +87,7 @@ public abstract class SegmentBase { ...@@ -89,7 +87,7 @@ public abstract class SegmentBase {
* @param indexLength The length of the index data in bytes. * @param indexLength The length of the index data in bytes.
*/ */
public SingleSegmentBase(RangedUri initialization, long timescale, long presentationTimeOffset, public SingleSegmentBase(RangedUri initialization, long timescale, long presentationTimeOffset,
Uri uri, long indexStart, long indexLength) { String uri, long indexStart, long indexLength) {
super(initialization, timescale, presentationTimeOffset); super(initialization, timescale, presentationTimeOffset);
this.uri = uri; this.uri = uri;
this.indexStart = indexStart; this.indexStart = indexStart;
...@@ -99,7 +97,7 @@ public abstract class SegmentBase { ...@@ -99,7 +97,7 @@ public abstract class SegmentBase {
/** /**
* @param uri The uri of the segment. * @param uri The uri of the segment.
*/ */
public SingleSegmentBase(Uri uri) { public SingleSegmentBase(String uri) {
this(null, 1, 0, uri, 0, -1); this(null, 1, 0, uri, 0, -1);
} }
...@@ -289,7 +287,7 @@ public abstract class SegmentBase { ...@@ -289,7 +287,7 @@ public abstract class SegmentBase {
/* package */ final UrlTemplate initializationTemplate; /* package */ final UrlTemplate initializationTemplate;
/* package */ final UrlTemplate mediaTemplate; /* package */ final UrlTemplate mediaTemplate;
private final Uri baseUrl; private final String baseUrl;
/** /**
* @param initialization A {@link RangedUri} corresponding to initialization data, if such data * @param initialization A {@link RangedUri} corresponding to initialization data, if such data
...@@ -315,7 +313,7 @@ public abstract class SegmentBase { ...@@ -315,7 +313,7 @@ public abstract class SegmentBase {
public SegmentTemplate(RangedUri initialization, long timescale, long presentationTimeOffset, public SegmentTemplate(RangedUri initialization, long timescale, long presentationTimeOffset,
long periodDurationMs, int startNumber, long duration, long periodDurationMs, int startNumber, long duration,
List<SegmentTimelineElement> segmentTimeline, UrlTemplate initializationTemplate, List<SegmentTimelineElement> segmentTimeline, UrlTemplate initializationTemplate,
UrlTemplate mediaTemplate, Uri baseUrl) { UrlTemplate mediaTemplate, String baseUrl) {
super(initialization, timescale, presentationTimeOffset, periodDurationMs, startNumber, super(initialization, timescale, presentationTimeOffset, periodDurationMs, startNumber,
duration, segmentTimeline); duration, segmentTimeline);
this.initializationTemplate = initializationTemplate; this.initializationTemplate = initializationTemplate;
......
...@@ -27,6 +27,7 @@ import com.google.android.exoplayer.upstream.DataSource; ...@@ -27,6 +27,7 @@ import com.google.android.exoplayer.upstream.DataSource;
import com.google.android.exoplayer.upstream.DataSpec; import com.google.android.exoplayer.upstream.DataSpec;
import com.google.android.exoplayer.upstream.HttpDataSource.InvalidResponseCodeException; import com.google.android.exoplayer.upstream.HttpDataSource.InvalidResponseCodeException;
import com.google.android.exoplayer.util.Assertions; import com.google.android.exoplayer.util.Assertions;
import com.google.android.exoplayer.util.UriUtil;
import com.google.android.exoplayer.util.Util; import com.google.android.exoplayer.util.Util;
import android.net.Uri; import android.net.Uri;
...@@ -121,7 +122,7 @@ public class HlsChunkSource { ...@@ -121,7 +122,7 @@ public class HlsChunkSource {
private final Variant[] enabledVariants; private final Variant[] enabledVariants;
private final BandwidthMeter bandwidthMeter; private final BandwidthMeter bandwidthMeter;
private final int adaptiveMode; private final int adaptiveMode;
private final Uri baseUri; private final String baseUri;
private final int maxWidth; private final int maxWidth;
private final int maxHeight; private final int maxHeight;
private final int targetBufferSize; private final int targetBufferSize;
...@@ -301,11 +302,11 @@ public class HlsChunkSource { ...@@ -301,11 +302,11 @@ public class HlsChunkSource {
} }
HlsMediaPlaylist.Segment segment = mediaPlaylist.segments.get(chunkIndex); HlsMediaPlaylist.Segment segment = mediaPlaylist.segments.get(chunkIndex);
Uri chunkUri = Util.getMergedUri(mediaPlaylist.baseUri, segment.url); Uri chunkUri = UriUtil.resolveToUri(mediaPlaylist.baseUri, segment.url);
// Check if encryption is specified. // Check if encryption is specified.
if (HlsMediaPlaylist.ENCRYPTION_METHOD_AES_128.equals(segment.encryptionMethod)) { if (HlsMediaPlaylist.ENCRYPTION_METHOD_AES_128.equals(segment.encryptionMethod)) {
Uri keyUri = Util.getMergedUri(mediaPlaylist.baseUri, segment.encryptionKeyUri); Uri keyUri = UriUtil.resolveToUri(mediaPlaylist.baseUri, segment.encryptionKeyUri);
if (!keyUri.equals(encryptionKeyUri)) { if (!keyUri.equals(encryptionKeyUri)) {
// Encryption is specified and the key has changed. // Encryption is specified and the key has changed.
HlsChunk toReturn = newEncryptionKeyChunk(keyUri, segment.encryptionIV); HlsChunk toReturn = newEncryptionKeyChunk(keyUri, segment.encryptionIV);
...@@ -437,7 +438,7 @@ public class HlsChunkSource { ...@@ -437,7 +438,7 @@ public class HlsChunkSource {
} }
private MediaPlaylistChunk newMediaPlaylistChunk(int variantIndex) { private MediaPlaylistChunk newMediaPlaylistChunk(int variantIndex) {
Uri mediaPlaylistUri = Util.getMergedUri(baseUri, enabledVariants[variantIndex].url); Uri mediaPlaylistUri = UriUtil.resolveToUri(baseUri, enabledVariants[variantIndex].url);
DataSpec dataSpec = new DataSpec(mediaPlaylistUri, 0, C.LENGTH_UNBOUNDED, null, DataSpec dataSpec = new DataSpec(mediaPlaylistUri, 0, C.LENGTH_UNBOUNDED, null,
DataSpec.FLAG_ALLOW_GZIP); DataSpec.FLAG_ALLOW_GZIP);
return new MediaPlaylistChunk(variantIndex, upstreamDataSource, dataSpec, return new MediaPlaylistChunk(variantIndex, upstreamDataSource, dataSpec,
......
...@@ -15,8 +15,6 @@ ...@@ -15,8 +15,6 @@
*/ */
package com.google.android.exoplayer.hls; package com.google.android.exoplayer.hls;
import android.net.Uri;
import java.util.List; import java.util.List;
/** /**
...@@ -26,7 +24,7 @@ public final class HlsMasterPlaylist extends HlsPlaylist { ...@@ -26,7 +24,7 @@ public final class HlsMasterPlaylist extends HlsPlaylist {
public final List<Variant> variants; public final List<Variant> variants;
public HlsMasterPlaylist(Uri baseUri, List<Variant> variants) { public HlsMasterPlaylist(String baseUri, List<Variant> variants) {
super(baseUri, HlsPlaylist.TYPE_MASTER); super(baseUri, HlsPlaylist.TYPE_MASTER);
this.variants = variants; this.variants = variants;
} }
......
...@@ -17,8 +17,6 @@ package com.google.android.exoplayer.hls; ...@@ -17,8 +17,6 @@ package com.google.android.exoplayer.hls;
import com.google.android.exoplayer.C; import com.google.android.exoplayer.C;
import android.net.Uri;
import java.util.List; import java.util.List;
/** /**
...@@ -70,7 +68,7 @@ public final class HlsMediaPlaylist extends HlsPlaylist { ...@@ -70,7 +68,7 @@ public final class HlsMediaPlaylist extends HlsPlaylist {
public final boolean live; public final boolean live;
public final long durationUs; public final long durationUs;
public HlsMediaPlaylist(Uri baseUri, int mediaSequence, int targetDurationSecs, int version, public HlsMediaPlaylist(String baseUri, int mediaSequence, int targetDurationSecs, int version,
boolean live, List<Segment> segments) { boolean live, List<Segment> segments) {
super(baseUri, HlsPlaylist.TYPE_MEDIA); super(baseUri, HlsPlaylist.TYPE_MEDIA);
this.mediaSequence = mediaSequence; this.mediaSequence = mediaSequence;
......
...@@ -15,7 +15,6 @@ ...@@ -15,7 +15,6 @@
*/ */
package com.google.android.exoplayer.hls; package com.google.android.exoplayer.hls;
import android.net.Uri;
/** /**
* Represents an HLS playlist. * Represents an HLS playlist.
...@@ -25,10 +24,10 @@ public abstract class HlsPlaylist { ...@@ -25,10 +24,10 @@ public abstract class HlsPlaylist {
public final static int TYPE_MASTER = 0; public final static int TYPE_MASTER = 0;
public final static int TYPE_MEDIA = 1; public final static int TYPE_MEDIA = 1;
public final Uri baseUri; public final String baseUri;
public final int type; public final int type;
protected HlsPlaylist(Uri baseUri, int type) { protected HlsPlaylist(String baseUri, int type) {
this.baseUri = baseUri; this.baseUri = baseUri;
this.type = type; this.type = type;
} }
......
...@@ -19,9 +19,6 @@ import com.google.android.exoplayer.C; ...@@ -19,9 +19,6 @@ 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.upstream.NetworkLoadable; import com.google.android.exoplayer.upstream.NetworkLoadable;
import com.google.android.exoplayer.util.Util;
import android.net.Uri;
import java.io.BufferedReader; import java.io.BufferedReader;
import java.io.IOException; import java.io.IOException;
...@@ -86,7 +83,6 @@ public final class HlsPlaylistParser implements NetworkLoadable.Parser<HlsPlayli ...@@ -86,7 +83,6 @@ public final class HlsPlaylistParser implements NetworkLoadable.Parser<HlsPlayli
@Override @Override
public HlsPlaylist parse(String connectionUrl, InputStream inputStream) public HlsPlaylist parse(String connectionUrl, InputStream inputStream)
throws IOException, ParserException { throws IOException, ParserException {
Uri baseUri = Util.parseBaseUri(connectionUrl);
BufferedReader reader = new BufferedReader(new InputStreamReader(inputStream)); BufferedReader reader = new BufferedReader(new InputStreamReader(inputStream));
Queue<String> extraLines = new LinkedList<String>(); Queue<String> extraLines = new LinkedList<String>();
String line; String line;
...@@ -97,7 +93,7 @@ public final class HlsPlaylistParser implements NetworkLoadable.Parser<HlsPlayli ...@@ -97,7 +93,7 @@ public final class HlsPlaylistParser implements NetworkLoadable.Parser<HlsPlayli
// Do nothing. // Do nothing.
} else if (line.startsWith(STREAM_INF_TAG)) { } else if (line.startsWith(STREAM_INF_TAG)) {
extraLines.add(line); extraLines.add(line);
return parseMasterPlaylist(new LineIterator(extraLines, reader), baseUri); return parseMasterPlaylist(new LineIterator(extraLines, reader), connectionUrl);
} else if (line.startsWith(TARGET_DURATION_TAG) } else if (line.startsWith(TARGET_DURATION_TAG)
|| line.startsWith(MEDIA_SEQUENCE_TAG) || line.startsWith(MEDIA_SEQUENCE_TAG)
|| line.startsWith(MEDIA_DURATION_TAG) || line.startsWith(MEDIA_DURATION_TAG)
...@@ -106,7 +102,7 @@ public final class HlsPlaylistParser implements NetworkLoadable.Parser<HlsPlayli ...@@ -106,7 +102,7 @@ public final class HlsPlaylistParser implements NetworkLoadable.Parser<HlsPlayli
|| line.equals(DISCONTINUITY_TAG) || line.equals(DISCONTINUITY_TAG)
|| line.equals(ENDLIST_TAG)) { || line.equals(ENDLIST_TAG)) {
extraLines.add(line); extraLines.add(line);
return parseMediaPlaylist(new LineIterator(extraLines, reader), baseUri); return parseMediaPlaylist(new LineIterator(extraLines, reader), connectionUrl);
} else if (line.startsWith(VERSION_TAG)) { } else if (line.startsWith(VERSION_TAG)) {
extraLines.add(line); extraLines.add(line);
} else if (!line.startsWith("#")) { } else if (!line.startsWith("#")) {
...@@ -119,7 +115,7 @@ public final class HlsPlaylistParser implements NetworkLoadable.Parser<HlsPlayli ...@@ -119,7 +115,7 @@ public final class HlsPlaylistParser implements NetworkLoadable.Parser<HlsPlayli
throw new ParserException("Failed to parse the playlist, could not identify any tags."); throw new ParserException("Failed to parse the playlist, could not identify any tags.");
} }
private static HlsMasterPlaylist parseMasterPlaylist(LineIterator iterator, Uri baseUri) private static HlsMasterPlaylist parseMasterPlaylist(LineIterator iterator, String baseUri)
throws IOException { throws IOException {
List<Variant> variants = new ArrayList<Variant>(); List<Variant> variants = new ArrayList<Variant>();
int bandwidth = 0; int bandwidth = 0;
...@@ -160,7 +156,7 @@ public final class HlsPlaylistParser implements NetworkLoadable.Parser<HlsPlayli ...@@ -160,7 +156,7 @@ public final class HlsPlaylistParser implements NetworkLoadable.Parser<HlsPlayli
return new HlsMasterPlaylist(baseUri, Collections.unmodifiableList(variants)); return new HlsMasterPlaylist(baseUri, Collections.unmodifiableList(variants));
} }
private static HlsMediaPlaylist parseMediaPlaylist(LineIterator iterator, Uri baseUri) private static HlsMediaPlaylist parseMediaPlaylist(LineIterator iterator, String baseUri)
throws IOException { throws IOException {
int mediaSequence = 0; int mediaSequence = 0;
int targetDurationSecs = 0; int targetDurationSecs = 0;
......
...@@ -17,6 +17,7 @@ package com.google.android.exoplayer.smoothstreaming; ...@@ -17,6 +17,7 @@ package com.google.android.exoplayer.smoothstreaming;
import com.google.android.exoplayer.C; import com.google.android.exoplayer.C;
import com.google.android.exoplayer.util.Assertions; import com.google.android.exoplayer.util.Assertions;
import com.google.android.exoplayer.util.UriUtil;
import com.google.android.exoplayer.util.Util; import com.google.android.exoplayer.util.Util;
import android.net.Uri; import android.net.Uri;
...@@ -197,14 +198,14 @@ public class SmoothStreamingManifest { ...@@ -197,14 +198,14 @@ public class SmoothStreamingManifest {
public final TrackElement[] tracks; public final TrackElement[] tracks;
public final int chunkCount; public final int chunkCount;
private final Uri baseUri; private final String baseUri;
private final String chunkTemplate; private final String chunkTemplate;
private final List<Long> chunkStartTimes; private final List<Long> chunkStartTimes;
private final long[] chunkStartTimesUs; private final long[] chunkStartTimesUs;
private final long lastChunkDurationUs; private final long lastChunkDurationUs;
public StreamElement(Uri baseUri, String chunkTemplate, int type, String subType, public StreamElement(String baseUri, String chunkTemplate, int type, String subType,
long timescale, String name, int qualityLevels, int maxWidth, int maxHeight, long timescale, String name, int qualityLevels, int maxWidth, int maxHeight,
int displayWidth, int displayHeight, String language, TrackElement[] tracks, int displayWidth, int displayHeight, String language, TrackElement[] tracks,
List<Long> chunkStartTimes, long lastChunkDuration) { List<Long> chunkStartTimes, long lastChunkDuration) {
...@@ -274,7 +275,7 @@ public class SmoothStreamingManifest { ...@@ -274,7 +275,7 @@ public class SmoothStreamingManifest {
String chunkUrl = chunkTemplate String chunkUrl = chunkTemplate
.replace(URL_PLACEHOLDER_BITRATE, Integer.toString(tracks[track].bitrate)) .replace(URL_PLACEHOLDER_BITRATE, Integer.toString(tracks[track].bitrate))
.replace(URL_PLACEHOLDER_START_TIME, chunkStartTimes.get(chunkIndex).toString()); .replace(URL_PLACEHOLDER_START_TIME, chunkStartTimes.get(chunkIndex).toString());
return Util.getMergedUri(baseUri, chunkUrl); return UriUtil.resolveToUri(baseUri, chunkUrl);
} }
} }
......
...@@ -23,9 +23,7 @@ import com.google.android.exoplayer.upstream.NetworkLoadable; ...@@ -23,9 +23,7 @@ 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.Util;
import android.net.Uri;
import android.util.Base64; import android.util.Base64;
import android.util.Pair; import android.util.Pair;
...@@ -65,8 +63,8 @@ public class SmoothStreamingManifestParser implements ...@@ -65,8 +63,8 @@ public class SmoothStreamingManifestParser implements
try { try {
XmlPullParser xmlParser = xmlParserFactory.newPullParser(); XmlPullParser xmlParser = xmlParserFactory.newPullParser();
xmlParser.setInput(inputStream, null); xmlParser.setInput(inputStream, null);
SmoothStreamMediaParser smoothStreamMediaParser = new SmoothStreamMediaParser(null, SmoothStreamMediaParser smoothStreamMediaParser =
Util.parseBaseUri(connectionUrl)); new SmoothStreamMediaParser(null, 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);
...@@ -89,13 +87,13 @@ public class SmoothStreamingManifestParser implements ...@@ -89,13 +87,13 @@ public class SmoothStreamingManifestParser implements
*/ */
private static abstract class ElementParser { private static abstract class ElementParser {
private final Uri baseUri; private final String baseUri;
private final String tag; private final String tag;
private final ElementParser parent; private final ElementParser parent;
private final List<Pair<String, Object>> normalizedAttributes; private final List<Pair<String, Object>> normalizedAttributes;
public ElementParser(ElementParser parent, Uri baseUri, String tag) { public ElementParser(ElementParser parent, String baseUri, String tag) {
this.parent = parent; this.parent = parent;
this.baseUri = baseUri; this.baseUri = baseUri;
this.tag = tag; this.tag = tag;
...@@ -158,7 +156,7 @@ public class SmoothStreamingManifestParser implements ...@@ -158,7 +156,7 @@ public class SmoothStreamingManifestParser implements
} }
} }
private ElementParser newChildParser(ElementParser parent, String name, Uri baseUri) { private ElementParser newChildParser(ElementParser parent, String name, String baseUri) {
if (TrackElementParser.TAG.equals(name)) { if (TrackElementParser.TAG.equals(name)) {
return new TrackElementParser(parent, baseUri); return new TrackElementParser(parent, baseUri);
} else if (ProtectionElementParser.TAG.equals(name)) { } else if (ProtectionElementParser.TAG.equals(name)) {
...@@ -342,7 +340,7 @@ public class SmoothStreamingManifestParser implements ...@@ -342,7 +340,7 @@ public class SmoothStreamingManifestParser implements
private ProtectionElement protectionElement; private ProtectionElement protectionElement;
private List<StreamElement> streamElements; private List<StreamElement> streamElements;
public SmoothStreamMediaParser(ElementParser parent, Uri baseUri) { public SmoothStreamMediaParser(ElementParser parent, String baseUri) {
super(parent, baseUri, TAG); super(parent, baseUri, TAG);
lookAheadCount = -1; lookAheadCount = -1;
protectionElement = null; protectionElement = null;
...@@ -392,7 +390,7 @@ public class SmoothStreamingManifestParser implements ...@@ -392,7 +390,7 @@ public class SmoothStreamingManifestParser implements
private UUID uuid; private UUID uuid;
private byte[] initData; private byte[] initData;
public ProtectionElementParser(ElementParser parent, Uri baseUri) { public ProtectionElementParser(ElementParser parent, String baseUri) {
super(parent, baseUri, TAG); super(parent, baseUri, TAG);
} }
...@@ -455,7 +453,7 @@ public class SmoothStreamingManifestParser implements ...@@ -455,7 +453,7 @@ public class SmoothStreamingManifestParser implements
private static final String KEY_FRAGMENT_START_TIME = "t"; private static final String KEY_FRAGMENT_START_TIME = "t";
private static final String KEY_FRAGMENT_REPEAT_COUNT = "r"; private static final String KEY_FRAGMENT_REPEAT_COUNT = "r";
private final Uri baseUri; private final String baseUri;
private final List<TrackElement> tracks; private final List<TrackElement> tracks;
private int type; private int type;
...@@ -473,7 +471,7 @@ public class SmoothStreamingManifestParser implements ...@@ -473,7 +471,7 @@ public class SmoothStreamingManifestParser implements
private long lastChunkDuration; private long lastChunkDuration;
public StreamElementParser(ElementParser parent, Uri baseUri) { public StreamElementParser(ElementParser parent, String baseUri) {
super(parent, baseUri, TAG); super(parent, baseUri, TAG);
this.baseUri = baseUri; this.baseUri = baseUri;
tracks = new LinkedList<TrackElement>(); tracks = new LinkedList<TrackElement>();
...@@ -615,7 +613,7 @@ public class SmoothStreamingManifestParser implements ...@@ -615,7 +613,7 @@ public class SmoothStreamingManifestParser implements
private int nalUnitLengthField; private int nalUnitLengthField;
private String content; private String content;
public TrackElementParser(ElementParser parent, Uri baseUri) { public TrackElementParser(ElementParser parent, String baseUri) {
super(parent, baseUri, TAG); super(parent, baseUri, TAG);
this.csd = new LinkedList<byte[]>(); this.csd = new LinkedList<byte[]>();
} }
......
/*
* 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 android.net.Uri;
import android.text.TextUtils;
/**
* Utility methods for manipulating URIs.
*/
public final class UriUtil {
/**
* The length of arrays returned by {@link #getUriIndices(String)}.
*/
private static final int INDEX_COUNT = 4;
/**
* An index into an array returned by {@link #getUriIndices(String)}.
* <p>
* The value at this position in the array is the index of the ':' after the scheme. Equals -1 if
* the URI is a relative reference (no scheme). The hier-part starts at (schemeColon + 1),
* including when the URI has no scheme.
*/
private static final int SCHEME_COLON = 0;
/**
* An index into an array returned by {@link #getUriIndices(String)}.
* <p>
* The value at this position in the array is the index of the path part. Equals (schemeColon + 1)
* if no authority part, (schemeColon + 3) if the authority part consists of just "//", and
* (query) if no path part. The characters starting at this index can be "//" only if the
* authority part is non-empty (in this case the double-slash means the first segment is empty).
*/
private static final int PATH = 1;
/**
* An index into an array returned by {@link #getUriIndices(String)}.
* <p>
* The value at this position in the array is the index of the query part, including the '?'
* before the query. Equals fragment if no query part, and (fragment - 1) if the query part is a
* single '?' with no data.
*/
private static final int QUERY = 2;
/**
* An index into an array returned by {@link #getUriIndices(String)}.
* <p>
* The value at this position in the array is the index of the fragment part, including the '#'
* before the fragment. Equal to the length of the URI if no fragment part, and (length - 1) if
* the fragment part is a single '#' with no data.
*/
private static final int FRAGMENT = 3;
private UriUtil() {}
/**
* Like {@link #resolve(String, String)}, but returns a {@link Uri} instead of a {@link String}.
*
* @param baseUri The base URI.
* @param referenceUri The reference URI to resolve.
*/
public static Uri resolveToUri(String baseUri, String referenceUri) {
return Uri.parse(resolve(baseUri, referenceUri));
}
/**
* Performs relative resolution of a {@code referenceUri} with respect to a {@code baseUri}.
* <p>
* The resolution is performed as specified by RFC-3986.
*
* @param baseUri The base URI.
* @param referenceUri The reference URI to resolve.
*/
public static String resolve(String baseUri, String referenceUri) {
StringBuilder uri = new StringBuilder();
// Map null onto empty string, to make the following logic simpler.
baseUri = baseUri == null ? "" : baseUri;
referenceUri = referenceUri == null ? "" : referenceUri;
int[] refIndices = getUriIndices(referenceUri);
if (refIndices[SCHEME_COLON] != -1) {
// The reference is absolute. The target Uri is the reference.
uri.append(referenceUri);
removeDotSegments(uri, refIndices[PATH], refIndices[QUERY]);
return uri.toString();
}
int[] baseIndices = getUriIndices(baseUri);
if (refIndices[FRAGMENT] == 0) {
// The reference is empty or contains just the fragment part, then the target Uri is the
// concatenation of the base Uri without its fragment, and the reference.
return uri.append(baseUri, 0, baseIndices[FRAGMENT]).append(referenceUri).toString();
}
if (refIndices[QUERY] == 0) {
// The reference starts with the query part. The target is the base up to (but excluding) the
// query, plus the reference.
return uri.append(baseUri, 0, baseIndices[QUERY]).append(referenceUri).toString();
}
if (refIndices[PATH] != 0) {
// The reference has authority. The target is the base scheme plus the reference.
int baseLimit = baseIndices[SCHEME_COLON] + 1;
uri.append(baseUri, 0, baseLimit).append(referenceUri);
return removeDotSegments(uri, baseLimit + refIndices[PATH], baseLimit + refIndices[QUERY]);
}
if (refIndices[PATH] != refIndices[QUERY] && referenceUri.charAt(refIndices[PATH]) == '/') {
// The reference path is rooted. The target is the base scheme and authority (if any), plus
// the reference.
uri.append(baseUri, 0, baseIndices[PATH]).append(referenceUri);
return removeDotSegments(uri, baseIndices[PATH], baseIndices[PATH] + refIndices[QUERY]);
}
// The target Uri is the concatenation of the base Uri up to (but excluding) the last segment,
// and the reference. This can be split into 2 cases:
if (baseIndices[SCHEME_COLON] + 2 < baseIndices[PATH]
&& baseIndices[PATH] == baseIndices[QUERY]) {
// Case 1: The base hier-part is just the authority, with an empty path. An additional '/' is
// needed after the authority, before appending the reference.
uri.append(baseUri, 0, baseIndices[PATH]).append('/').append(referenceUri);
return removeDotSegments(uri, baseIndices[PATH], baseIndices[PATH] + refIndices[QUERY] + 1);
} else {
// Case 2: Otherwise, find the last '/' in the base hier-part and append the reference after
// it. If base hier-part has no '/', it could only mean that it is completely empty or
// contains only one segment, in which case the whole hier-part is excluded and the reference
// is appended right after the base scheme colon without an added '/'.
int lastSlashIndex = baseUri.lastIndexOf('/', baseIndices[QUERY] - 1);
int baseLimit = lastSlashIndex == -1 ? baseIndices[PATH] : lastSlashIndex + 1;
uri.append(baseUri, 0, baseLimit).append(referenceUri);
return removeDotSegments(uri, baseIndices[PATH], baseLimit + refIndices[QUERY]);
}
}
/**
* Removes dot segments from the path of a URI.
*
* @param uri A {@link StringBuilder} containing the URI.
* @param offset The index of the start of the path in {@code uri}.
* @param limit The limit (exclusive) of the path in {@code uri}.
*/
private static String removeDotSegments(StringBuilder uri, int offset, int limit) {
if (offset >= limit) {
// Nothing to do.
return uri.toString();
}
if (uri.charAt(offset) == '/') {
// If the path starts with a /, always retain it.
offset++;
}
// The first character of the current path segment.
int segmentStart = offset;
int i = offset;
while (i <= limit) {
int nextSegmentStart = -1;
if (i == limit) {
nextSegmentStart = i;
} else if (uri.charAt(i) == '/') {
nextSegmentStart = i + 1;
} else {
i++;
continue;
}
// We've encountered the end of a segment or the end of the path. If the final segment was
// "." or "..", remove the appropriate segments of the path.
if (i == segmentStart + 1 && uri.charAt(segmentStart) == '.') {
// Given "abc/def/./ghi", remove "./" to get "abc/def/ghi".
uri.delete(segmentStart, nextSegmentStart);
limit -= nextSegmentStart - segmentStart;
i = segmentStart;
} else if (i == segmentStart + 2 && uri.charAt(segmentStart) == '.'
&& uri.charAt(segmentStart + 1) == '.') {
// Given "abc/def/../ghi", remove "def/../" to get "abc/ghi".
int prevSegmentStart = uri.lastIndexOf("/", segmentStart - 2) + 1;
int removeFrom = prevSegmentStart > offset ? prevSegmentStart : offset;
uri.delete(removeFrom, nextSegmentStart);
limit -= nextSegmentStart - removeFrom;
segmentStart = prevSegmentStart;
i = prevSegmentStart;
} else {
i++;
segmentStart = i;
}
}
return uri.toString();
}
/**
* Calculates indices of the constituent components of a URI.
*
* @param uriString The URI as a string.
* @return The corresponding indices.
*/
private static int[] getUriIndices(String uriString) {
int[] indices = new int[INDEX_COUNT];
if (TextUtils.isEmpty(uriString)) {
indices[SCHEME_COLON] = -1;
return indices;
}
// Determine outer structure from right to left.
// Uri = scheme ":" hier-part [ "?" query ] [ "#" fragment ]
int length = uriString.length();
int fragmentIndex = uriString.indexOf('#');
if (fragmentIndex == -1) {
fragmentIndex = length;
}
int queryIndex = uriString.indexOf('?');
if (queryIndex == -1 || queryIndex > fragmentIndex) {
// '#' before '?': '?' is within the fragment.
queryIndex = fragmentIndex;
}
// Slashes are allowed only in hier-part so any colon after the first slash is part of the
// hier-part, not the scheme colon separator.
int schemeIndexLimit = uriString.indexOf('/');
if (schemeIndexLimit == -1 || schemeIndexLimit > queryIndex) {
schemeIndexLimit = queryIndex;
}
int schemeIndex = uriString.indexOf(':');
if (schemeIndex > schemeIndexLimit) {
// '/' before ':'
schemeIndex = -1;
}
// Determine hier-part structure: hier-part = "//" authority path / path
// This block can also cope with schemeIndex == -1.
boolean hasAuthority = schemeIndex + 2 < queryIndex
&& uriString.charAt(schemeIndex + 1) == '/'
&& uriString.charAt(schemeIndex + 2) == '/';
int pathIndex;
if (hasAuthority) {
pathIndex = uriString.indexOf('/', schemeIndex + 3); // find first '/' after "://"
if (pathIndex == -1 || pathIndex > queryIndex) {
pathIndex = queryIndex;
}
} else {
pathIndex = schemeIndex + 1;
}
indices[SCHEME_COLON] = schemeIndex;
indices[PATH] = pathIndex;
indices[QUERY] = queryIndex;
indices[FRAGMENT] = fragmentIndex;
return indices;
}
}
...@@ -17,7 +17,6 @@ package com.google.android.exoplayer.util; ...@@ -17,7 +17,6 @@ package com.google.android.exoplayer.util;
import com.google.android.exoplayer.upstream.DataSource; import com.google.android.exoplayer.upstream.DataSource;
import android.net.Uri;
import android.text.TextUtils; import android.text.TextUtils;
import java.io.IOException; import java.io.IOException;
...@@ -135,54 +134,6 @@ public final class Util { ...@@ -135,54 +134,6 @@ public final class Util {
} }
/** /**
* Like {@link Uri#parse(String)}, but discards the part of the uri that follows the final
* forward slash.
*
* @param uriString An RFC 2396-compliant, encoded uri.
* @return The parsed base uri.
*/
public static Uri parseBaseUri(String uriString) {
return Uri.parse(uriString.substring(0, uriString.lastIndexOf('/')));
}
/**
* Merges a uri and a string to produce a new uri.
* <p>
* The uri is built according to the following rules:
* <ul>
* <li>If {@code baseUri} is null or if {@code stringUri} is absolute, then {@code baseUri} is
* ignored and the uri consists solely of {@code stringUri}.
* <li>If {@code stringUri} is null, then the uri consists solely of {@code baseUrl}.
* <li>Otherwise, the uri consists of the concatenation of {@code baseUri} and {@code stringUri}.
* </ul>
*
* @param baseUri A uri that can form the base of the merged uri.
* @param stringUri A relative or absolute uri in string form.
* @return The merged uri.
*/
public static Uri getMergedUri(Uri baseUri, String stringUri) {
if (stringUri == null) {
return baseUri;
}
if (baseUri == null) {
return Uri.parse(stringUri);
}
if (stringUri.startsWith("/")) {
stringUri = stringUri.substring(1);
return new Uri.Builder()
.scheme(baseUri.getScheme())
.authority(baseUri.getAuthority())
.appendEncodedPath(stringUri)
.build();
}
Uri uri = Uri.parse(stringUri);
if (uri.isAbsolute()) {
return uri;
}
return Uri.withAppendedPath(baseUri, stringUri);
}
/**
* Returns the index of the largest value in an array that is less than (or optionally equal to) * Returns the index of the largest value in an array that is less than (or optionally equal to)
* a specified key. * a specified key.
* <p> * <p>
......
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