diff --git a/library/src/main/java/com/google/android/exoplayer/dash/DashChunkSource.java b/library/src/main/java/com/google/android/exoplayer/dash/DashChunkSource.java index 7186412722..412c645eb4 100644 --- a/library/src/main/java/com/google/android/exoplayer/dash/DashChunkSource.java +++ b/library/src/main/java/com/google/android/exoplayer/dash/DashChunkSource.java @@ -581,7 +581,7 @@ public class DashChunkSource implements ChunkSource { } if ((result & Extractor.RESULT_READ_INDEX) != 0) { representationHolders.get(format.id).segmentIndex = - new DashWrappingSegmentIndex(extractor.getIndex(), uri, indexAnchor); + new DashWrappingSegmentIndex(extractor.getIndex(), uri.toString(), indexAnchor); } } diff --git a/library/src/main/java/com/google/android/exoplayer/dash/DashWrappingSegmentIndex.java b/library/src/main/java/com/google/android/exoplayer/dash/DashWrappingSegmentIndex.java index 44648469af..d62d6ee5c2 100644 --- a/library/src/main/java/com/google/android/exoplayer/dash/DashWrappingSegmentIndex.java +++ b/library/src/main/java/com/google/android/exoplayer/dash/DashWrappingSegmentIndex.java @@ -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.util.Util; -import android.net.Uri; - /** * An implementation of {@link DashSegmentIndex} that wraps a {@link SegmentIndex} parsed from a * media stream. @@ -28,16 +26,16 @@ import android.net.Uri; public class DashWrappingSegmentIndex implements DashSegmentIndex { private final SegmentIndex segmentIndex; - private final Uri uri; + private final String uri; private final long indexAnchor; /** * @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 * 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.uri = uri; this.indexAnchor = indexAnchor; diff --git a/library/src/main/java/com/google/android/exoplayer/dash/mpd/MediaPresentationDescriptionParser.java b/library/src/main/java/com/google/android/exoplayer/dash/mpd/MediaPresentationDescriptionParser.java index 813ce0c4e0..86722c0020 100644 --- a/library/src/main/java/com/google/android/exoplayer/dash/mpd/MediaPresentationDescriptionParser.java +++ b/library/src/main/java/com/google/android/exoplayer/dash/mpd/MediaPresentationDescriptionParser.java @@ -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.util.Assertions; import com.google.android.exoplayer.util.MimeTypes; +import com.google.android.exoplayer.util.UriUtil; import com.google.android.exoplayer.util.Util; -import android.net.Uri; import android.text.TextUtils; import org.xml.sax.helpers.DefaultHandler; @@ -83,7 +83,7 @@ public class MediaPresentationDescriptionParser extends DefaultHandler throw new ParserException( "inputStream does not contain a valid media presentation description"); } - return parseMediaPresentationDescription(xpp, Util.parseBaseUri(connectionUrl)); + return parseMediaPresentationDescription(xpp, connectionUrl); } catch (XmlPullParserException e) { throw new ParserException(e); } catch (ParseException e) { @@ -92,7 +92,7 @@ public class MediaPresentationDescriptionParser extends DefaultHandler } protected MediaPresentationDescription parseMediaPresentationDescription(XmlPullParser xpp, - Uri baseUrl) throws XmlPullParserException, IOException, ParseException { + String baseUrl) throws XmlPullParserException, IOException, ParseException { long availabilityStartTime = parseDateTime(xpp, "availabilityStartTime", -1); long durationMs = parseDuration(xpp, "mediaPresentationDuration", -1); long minBufferTimeMs = parseDuration(xpp, "minBufferTime", -1); @@ -137,7 +137,7 @@ public class MediaPresentationDescriptionParser extends DefaultHandler 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 { String id = xpp.getAttributeValue(null, "id"); long startMs = parseDuration(xpp, "start", 0); @@ -170,7 +170,7 @@ public class MediaPresentationDescriptionParser extends DefaultHandler // 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 { String mimeType = xpp.getAttributeValue(null, "mimeType"); @@ -287,9 +287,9 @@ public class MediaPresentationDescriptionParser extends DefaultHandler // Representation parsing. - protected Representation parseRepresentation(XmlPullParser xpp, Uri baseUrl, long periodStartMs, - long periodDurationMs, String mimeType, String language, SegmentBase segmentBase) - throws XmlPullParserException, IOException { + protected Representation parseRepresentation(XmlPullParser xpp, String baseUrl, + long periodStartMs, long periodDurationMs, String mimeType, String language, + SegmentBase segmentBase) throws XmlPullParserException, IOException { String id = xpp.getAttributeValue(null, "id"); int bandwidth = parseInt(xpp, "bandwidth"); int audioSamplingRate = parseInt(xpp, "audioSamplingRate"); @@ -335,7 +335,7 @@ public class MediaPresentationDescriptionParser extends DefaultHandler // SegmentBase, SegmentList and SegmentTemplate parsing. - protected SingleSegmentBase parseSegmentBase(XmlPullParser xpp, Uri baseUrl, + protected SingleSegmentBase parseSegmentBase(XmlPullParser xpp, String baseUrl, SingleSegmentBase parent) throws XmlPullParserException, IOException { long timescale = parseLong(xpp, "timescale", parent != null ? parent.timescale : 1); @@ -364,12 +364,12 @@ public class MediaPresentationDescriptionParser extends DefaultHandler } 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, 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 timescale = parseLong(xpp, "timescale", parent != null ? parent.timescale : 1); @@ -413,7 +413,7 @@ public class MediaPresentationDescriptionParser extends DefaultHandler 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 { long timescale = parseLong(xpp, "timescale", parent != null ? parent.timescale : 1); @@ -450,7 +450,7 @@ public class MediaPresentationDescriptionParser extends DefaultHandler protected SegmentTemplate buildSegmentTemplate(RangedUri initialization, long timescale, long presentationTimeOffset, long periodDurationMs, int startNumber, long duration, List timeline, UrlTemplate initializationTemplate, - UrlTemplate mediaTemplate, Uri baseUrl) { + UrlTemplate mediaTemplate, String baseUrl) { return new SegmentTemplate(initialization, timescale, presentationTimeOffset, periodDurationMs, startNumber, duration, timeline, initializationTemplate, mediaTemplate, baseUrl); } @@ -487,15 +487,15 @@ public class MediaPresentationDescriptionParser extends DefaultHandler return defaultValue; } - protected RangedUri parseInitialization(XmlPullParser xpp, Uri baseUrl) { + protected RangedUri parseInitialization(XmlPullParser xpp, String baseUrl) { 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"); } - protected RangedUri parseRangedUrl(XmlPullParser xpp, Uri baseUrl, String urlAttribute, + protected RangedUri parseRangedUrl(XmlPullParser xpp, String baseUrl, String urlAttribute, String rangeAttribute) { String urlText = xpp.getAttributeValue(null, urlAttribute); long rangeStart = 0; @@ -509,7 +509,7 @@ public class MediaPresentationDescriptionParser extends DefaultHandler 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) { return new RangedUri(baseUrl, urlText, rangeStart, rangeLength); } @@ -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 { xpp.next(); - String newBaseUrlText = xpp.getText(); - Uri newBaseUri = Uri.parse(newBaseUrlText); - if (!newBaseUri.isAbsolute()) { - newBaseUri = Uri.withAppendedPath(parentBaseUrl, newBaseUrlText); - } - return newBaseUri; + return UriUtil.resolve(parentBaseUrl, xpp.getText()); } protected static int parseInt(XmlPullParser xpp, String name) { diff --git a/library/src/main/java/com/google/android/exoplayer/dash/mpd/RangedUri.java b/library/src/main/java/com/google/android/exoplayer/dash/mpd/RangedUri.java index 2ce5ad3092..22a8bfdee5 100644 --- a/library/src/main/java/com/google/android/exoplayer/dash/mpd/RangedUri.java +++ b/library/src/main/java/com/google/android/exoplayer/dash/mpd/RangedUri.java @@ -16,7 +16,7 @@ package com.google.android.exoplayer.dash.mpd; 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; @@ -35,31 +35,28 @@ public final class RangedUri { */ public final long length; - // The {@link Uri} is stored internally in two parts, {@link #baseUri} and {@link uriString}. - // This helps optimize memory usage in the same way that DASH manifests allow many URLs to be - // expressed concisely in the form of a single BaseURL and many relative paths. Note that this - // optimization relies on the same {@code Uri} being passed as the {@link #baseUri} to many + // The URI is stored internally in two parts: reference URI and a base URI to use when + // resolving it. This helps optimize memory usage in the same way that DASH manifests allow many + // URLs to be expressed concisely in the form of a single BaseURL and many relative paths. Note + // that this optimization relies on the same object being passed as the base URI to many // instances of this class. - private final Uri baseUri; - private final String stringUri; + private final String baseUri; + private final String referenceUri; private int hashCode; /** * Constructs an ranged uri. - *

- * 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 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 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) { - Assertions.checkArgument(baseUri != null || stringUri != null); + public RangedUri(String baseUri, String referenceUri, long start, long length) { + Assertions.checkArgument(baseUri != null || referenceUri != null); this.baseUri = baseUri; - this.stringUri = stringUri; + this.referenceUri = referenceUri; this.start = start; this.length = length; } @@ -70,7 +67,16 @@ public final class RangedUri { * @return The {@link Uri} represented by the instance. */ 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 { * @return The merged {@link RangedUri} if the merge was successful. Null otherwise. */ public RangedUri attemptMerge(RangedUri other) { - if (other == null || !getUri().equals(other.getUri())) { + if (other == null || !getUriString().equals(other.getUriString())) { return null; } 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); } 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); } else { return null; @@ -104,7 +110,7 @@ public final class RangedUri { int result = 17; result = 31 * result + (int) start; result = 31 * result + (int) length; - result = 31 * result + getUri().hashCode(); + result = 31 * result + getUriString().hashCode(); hashCode = result; } return hashCode; @@ -121,7 +127,7 @@ public final class RangedUri { RangedUri other = (RangedUri) obj; return this.start == other.start && this.length == other.length - && getUri().equals(other.getUri()); + && getUriString().equals(other.getUriString()); } } diff --git a/library/src/main/java/com/google/android/exoplayer/dash/mpd/Representation.java b/library/src/main/java/com/google/android/exoplayer/dash/mpd/Representation.java index afae71de23..c5b0fcdad4 100644 --- a/library/src/main/java/com/google/android/exoplayer/dash/mpd/Representation.java +++ b/library/src/main/java/com/google/android/exoplayer/dash/mpd/Representation.java @@ -147,7 +147,7 @@ public abstract class Representation { public static class SingleSegmentRepresentation extends Representation { /** - * The {@link Uri} of the single segment. + * The uri of the single segment. */ public final Uri uri; @@ -174,7 +174,7 @@ public abstract class Representation { * @param contentLength The content length, or -1 if unknown. */ 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) { RangedUri rangedUri = new RangedUri(uri, null, initializationStart, initializationEnd - initializationStart + 1); @@ -197,13 +197,13 @@ public abstract class Representation { public SingleSegmentRepresentation(long periodStartMs, long periodDurationMs, String contentId, long revisionId, Format format, SingleSegmentBase segmentBase, long contentLength) { super(periodStartMs, periodDurationMs, contentId, revisionId, format, segmentBase); - this.uri = segmentBase.uri; + this.uri = Uri.parse(segmentBase.uri); this.indexUri = segmentBase.getIndex(); this.contentLength = contentLength; // 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. 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 diff --git a/library/src/main/java/com/google/android/exoplayer/dash/mpd/SegmentBase.java b/library/src/main/java/com/google/android/exoplayer/dash/mpd/SegmentBase.java index c6eec00602..dffe7200fa 100644 --- a/library/src/main/java/com/google/android/exoplayer/dash/mpd/SegmentBase.java +++ b/library/src/main/java/com/google/android/exoplayer/dash/mpd/SegmentBase.java @@ -19,8 +19,6 @@ import com.google.android.exoplayer.C; import com.google.android.exoplayer.dash.DashSegmentIndex; import com.google.android.exoplayer.util.Util; -import android.net.Uri; - import java.util.List; /** @@ -73,7 +71,7 @@ public abstract class SegmentBase { /** * The uri of the segment. */ - public final Uri uri; + public final String uri; /* package */ final long indexStart; /* package */ final long indexLength; @@ -89,7 +87,7 @@ public abstract class SegmentBase { * @param indexLength The length of the index data in bytes. */ public SingleSegmentBase(RangedUri initialization, long timescale, long presentationTimeOffset, - Uri uri, long indexStart, long indexLength) { + String uri, long indexStart, long indexLength) { super(initialization, timescale, presentationTimeOffset); this.uri = uri; this.indexStart = indexStart; @@ -99,7 +97,7 @@ public abstract class SegmentBase { /** * @param uri The uri of the segment. */ - public SingleSegmentBase(Uri uri) { + public SingleSegmentBase(String uri) { this(null, 1, 0, uri, 0, -1); } @@ -289,7 +287,7 @@ public abstract class SegmentBase { /* package */ final UrlTemplate initializationTemplate; /* package */ final UrlTemplate mediaTemplate; - private final Uri baseUrl; + private final String baseUrl; /** * @param initialization A {@link RangedUri} corresponding to initialization data, if such data @@ -315,7 +313,7 @@ public abstract class SegmentBase { public SegmentTemplate(RangedUri initialization, long timescale, long presentationTimeOffset, long periodDurationMs, int startNumber, long duration, List segmentTimeline, UrlTemplate initializationTemplate, - UrlTemplate mediaTemplate, Uri baseUrl) { + UrlTemplate mediaTemplate, String baseUrl) { super(initialization, timescale, presentationTimeOffset, periodDurationMs, startNumber, duration, segmentTimeline); this.initializationTemplate = initializationTemplate; diff --git a/library/src/main/java/com/google/android/exoplayer/hls/HlsChunkSource.java b/library/src/main/java/com/google/android/exoplayer/hls/HlsChunkSource.java index 82a9f98c51..6995a4beb5 100644 --- a/library/src/main/java/com/google/android/exoplayer/hls/HlsChunkSource.java +++ b/library/src/main/java/com/google/android/exoplayer/hls/HlsChunkSource.java @@ -27,6 +27,7 @@ import com.google.android.exoplayer.upstream.DataSource; import com.google.android.exoplayer.upstream.DataSpec; import com.google.android.exoplayer.upstream.HttpDataSource.InvalidResponseCodeException; import com.google.android.exoplayer.util.Assertions; +import com.google.android.exoplayer.util.UriUtil; import com.google.android.exoplayer.util.Util; import android.net.Uri; @@ -121,7 +122,7 @@ public class HlsChunkSource { private final Variant[] enabledVariants; private final BandwidthMeter bandwidthMeter; private final int adaptiveMode; - private final Uri baseUri; + private final String baseUri; private final int maxWidth; private final int maxHeight; private final int targetBufferSize; @@ -301,11 +302,11 @@ public class HlsChunkSource { } 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. 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)) { // Encryption is specified and the key has changed. HlsChunk toReturn = newEncryptionKeyChunk(keyUri, segment.encryptionIV); @@ -437,7 +438,7 @@ public class HlsChunkSource { } 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.FLAG_ALLOW_GZIP); return new MediaPlaylistChunk(variantIndex, upstreamDataSource, dataSpec, diff --git a/library/src/main/java/com/google/android/exoplayer/hls/HlsMasterPlaylist.java b/library/src/main/java/com/google/android/exoplayer/hls/HlsMasterPlaylist.java index 7ce299df0d..0a2b008d2f 100644 --- a/library/src/main/java/com/google/android/exoplayer/hls/HlsMasterPlaylist.java +++ b/library/src/main/java/com/google/android/exoplayer/hls/HlsMasterPlaylist.java @@ -15,8 +15,6 @@ */ package com.google.android.exoplayer.hls; -import android.net.Uri; - import java.util.List; /** @@ -26,7 +24,7 @@ public final class HlsMasterPlaylist extends HlsPlaylist { public final List variants; - public HlsMasterPlaylist(Uri baseUri, List variants) { + public HlsMasterPlaylist(String baseUri, List variants) { super(baseUri, HlsPlaylist.TYPE_MASTER); this.variants = variants; } diff --git a/library/src/main/java/com/google/android/exoplayer/hls/HlsMediaPlaylist.java b/library/src/main/java/com/google/android/exoplayer/hls/HlsMediaPlaylist.java index 3e9f151c08..16e90083d0 100644 --- a/library/src/main/java/com/google/android/exoplayer/hls/HlsMediaPlaylist.java +++ b/library/src/main/java/com/google/android/exoplayer/hls/HlsMediaPlaylist.java @@ -17,8 +17,6 @@ package com.google.android.exoplayer.hls; import com.google.android.exoplayer.C; -import android.net.Uri; - import java.util.List; /** @@ -70,7 +68,7 @@ public final class HlsMediaPlaylist extends HlsPlaylist { public final boolean live; 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 segments) { super(baseUri, HlsPlaylist.TYPE_MEDIA); this.mediaSequence = mediaSequence; diff --git a/library/src/main/java/com/google/android/exoplayer/hls/HlsPlaylist.java b/library/src/main/java/com/google/android/exoplayer/hls/HlsPlaylist.java index 3c86328ba6..b6cd9dac9a 100644 --- a/library/src/main/java/com/google/android/exoplayer/hls/HlsPlaylist.java +++ b/library/src/main/java/com/google/android/exoplayer/hls/HlsPlaylist.java @@ -15,7 +15,6 @@ */ package com.google.android.exoplayer.hls; -import android.net.Uri; /** * Represents an HLS playlist. @@ -25,10 +24,10 @@ public abstract class HlsPlaylist { public final static int TYPE_MASTER = 0; public final static int TYPE_MEDIA = 1; - public final Uri baseUri; + public final String baseUri; public final int type; - protected HlsPlaylist(Uri baseUri, int type) { + protected HlsPlaylist(String baseUri, int type) { this.baseUri = baseUri; this.type = type; } diff --git a/library/src/main/java/com/google/android/exoplayer/hls/HlsPlaylistParser.java b/library/src/main/java/com/google/android/exoplayer/hls/HlsPlaylistParser.java index 8db2094c9f..3fe3d15fd5 100644 --- a/library/src/main/java/com/google/android/exoplayer/hls/HlsPlaylistParser.java +++ b/library/src/main/java/com/google/android/exoplayer/hls/HlsPlaylistParser.java @@ -19,9 +19,6 @@ import com.google.android.exoplayer.C; import com.google.android.exoplayer.ParserException; import com.google.android.exoplayer.hls.HlsMediaPlaylist.Segment; import com.google.android.exoplayer.upstream.NetworkLoadable; -import com.google.android.exoplayer.util.Util; - -import android.net.Uri; import java.io.BufferedReader; import java.io.IOException; @@ -86,7 +83,6 @@ public final class HlsPlaylistParser implements NetworkLoadable.Parser extraLines = new LinkedList(); String line; @@ -97,7 +93,7 @@ public final class HlsPlaylistParser implements NetworkLoadable.Parser variants = new ArrayList(); int bandwidth = 0; @@ -160,7 +156,7 @@ public final class HlsPlaylistParser implements NetworkLoadable.Parser chunkStartTimes; private final long[] chunkStartTimesUs; 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, int displayWidth, int displayHeight, String language, TrackElement[] tracks, List chunkStartTimes, long lastChunkDuration) { @@ -274,7 +275,7 @@ public class SmoothStreamingManifest { String chunkUrl = chunkTemplate .replace(URL_PLACEHOLDER_BITRATE, Integer.toString(tracks[track].bitrate)) .replace(URL_PLACEHOLDER_START_TIME, chunkStartTimes.get(chunkIndex).toString()); - return Util.getMergedUri(baseUri, chunkUrl); + return UriUtil.resolveToUri(baseUri, chunkUrl); } } diff --git a/library/src/main/java/com/google/android/exoplayer/smoothstreaming/SmoothStreamingManifestParser.java b/library/src/main/java/com/google/android/exoplayer/smoothstreaming/SmoothStreamingManifestParser.java index 5cfbc829e3..27dd7752da 100644 --- a/library/src/main/java/com/google/android/exoplayer/smoothstreaming/SmoothStreamingManifestParser.java +++ b/library/src/main/java/com/google/android/exoplayer/smoothstreaming/SmoothStreamingManifestParser.java @@ -23,9 +23,7 @@ import com.google.android.exoplayer.upstream.NetworkLoadable; import com.google.android.exoplayer.util.Assertions; import com.google.android.exoplayer.util.CodecSpecificDataUtil; 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.Pair; @@ -65,8 +63,8 @@ public class SmoothStreamingManifestParser implements try { XmlPullParser xmlParser = xmlParserFactory.newPullParser(); xmlParser.setInput(inputStream, null); - SmoothStreamMediaParser smoothStreamMediaParser = new SmoothStreamMediaParser(null, - Util.parseBaseUri(connectionUrl)); + SmoothStreamMediaParser smoothStreamMediaParser = + new SmoothStreamMediaParser(null, connectionUrl); return (SmoothStreamingManifest) smoothStreamMediaParser.parse(xmlParser); } catch (XmlPullParserException e) { throw new ParserException(e); @@ -89,13 +87,13 @@ public class SmoothStreamingManifestParser implements */ private static abstract class ElementParser { - private final Uri baseUri; + private final String baseUri; private final String tag; private final ElementParser parent; private final List> normalizedAttributes; - public ElementParser(ElementParser parent, Uri baseUri, String tag) { + public ElementParser(ElementParser parent, String baseUri, String tag) { this.parent = parent; this.baseUri = baseUri; this.tag = tag; @@ -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)) { return new TrackElementParser(parent, baseUri); } else if (ProtectionElementParser.TAG.equals(name)) { @@ -342,7 +340,7 @@ public class SmoothStreamingManifestParser implements private ProtectionElement protectionElement; private List streamElements; - public SmoothStreamMediaParser(ElementParser parent, Uri baseUri) { + public SmoothStreamMediaParser(ElementParser parent, String baseUri) { super(parent, baseUri, TAG); lookAheadCount = -1; protectionElement = null; @@ -392,7 +390,7 @@ public class SmoothStreamingManifestParser implements private UUID uuid; private byte[] initData; - public ProtectionElementParser(ElementParser parent, Uri baseUri) { + public ProtectionElementParser(ElementParser parent, String baseUri) { super(parent, baseUri, TAG); } @@ -455,7 +453,7 @@ public class SmoothStreamingManifestParser implements private static final String KEY_FRAGMENT_START_TIME = "t"; private static final String KEY_FRAGMENT_REPEAT_COUNT = "r"; - private final Uri baseUri; + private final String baseUri; private final List tracks; private int type; @@ -473,7 +471,7 @@ public class SmoothStreamingManifestParser implements private long lastChunkDuration; - public StreamElementParser(ElementParser parent, Uri baseUri) { + public StreamElementParser(ElementParser parent, String baseUri) { super(parent, baseUri, TAG); this.baseUri = baseUri; tracks = new LinkedList(); @@ -615,7 +613,7 @@ public class SmoothStreamingManifestParser implements private int nalUnitLengthField; private String content; - public TrackElementParser(ElementParser parent, Uri baseUri) { + public TrackElementParser(ElementParser parent, String baseUri) { super(parent, baseUri, TAG); this.csd = new LinkedList(); } diff --git a/library/src/main/java/com/google/android/exoplayer/util/UriUtil.java b/library/src/main/java/com/google/android/exoplayer/util/UriUtil.java new file mode 100644 index 0000000000..61eb8fa0a4 --- /dev/null +++ b/library/src/main/java/com/google/android/exoplayer/util/UriUtil.java @@ -0,0 +1,258 @@ +/* + * 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)}. + *

+ * 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)}. + *

+ * 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)}. + *

+ * 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)}. + *

+ * 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}. + *

+ * 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; + } + +} diff --git a/library/src/main/java/com/google/android/exoplayer/util/Util.java b/library/src/main/java/com/google/android/exoplayer/util/Util.java index 7e096cfa1b..5081532f7a 100644 --- a/library/src/main/java/com/google/android/exoplayer/util/Util.java +++ b/library/src/main/java/com/google/android/exoplayer/util/Util.java @@ -17,7 +17,6 @@ package com.google.android.exoplayer.util; import com.google.android.exoplayer.upstream.DataSource; -import android.net.Uri; import android.text.TextUtils; import java.io.IOException; @@ -134,54 +133,6 @@ public final class Util { return text == null ? null : text.toLowerCase(Locale.US); } - /** - * 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. - *

- * The uri is built according to the following rules: - *

- * - * @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) * a specified key.