Skip to content

Commit

Permalink
Add support for relative baseUrls in DASH manifests.
Browse files Browse the repository at this point in the history
Ref: Issue #2
  • Loading branch information
ojw28 committed Jul 9, 2014
1 parent 1b95726 commit 9e16dec
Show file tree
Hide file tree
Showing 5 changed files with 74 additions and 33 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,8 @@
import com.google.android.exoplayer.ParserException;
import com.google.android.exoplayer.util.ManifestFetcher;

import android.net.Uri;

import org.xmlpull.v1.XmlPullParserException;

import java.io.IOException;
Expand Down Expand Up @@ -57,9 +59,9 @@ public MediaPresentationDescriptionFetcher(

@Override
protected MediaPresentationDescription parse(InputStream stream, String inputEncoding,
String contentId) throws IOException, ParserException {
String contentId, Uri baseUrl) throws IOException, ParserException {
try {
return parser.parseMediaPresentationDescription(stream, inputEncoding, contentId);
return parser.parseMediaPresentationDescription(stream, inputEncoding, contentId, baseUrl);
} catch (XmlPullParserException e) {
throw new ParserException(e);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -64,26 +64,28 @@ public MediaPresentationDescriptionParser() {
* @param inputStream The stream from which to parse the manifest.
* @param inputEncoding The encoding of the input.
* @param contentId The content id of the media.
* @param baseUrl The url that any relative urls defined within the manifest are relative to.
* @return The parsed manifest.
* @throws IOException If a problem occurred reading from the stream.
* @throws XmlPullParserException If a problem occurred parsing the stream as xml.
* @throws ParserException If a problem occurred parsing the xml as a DASH mpd.
*/
public MediaPresentationDescription parseMediaPresentationDescription(InputStream inputStream,
String inputEncoding, String contentId) throws XmlPullParserException, IOException,
ParserException {
String inputEncoding, String contentId, Uri baseUrl) throws XmlPullParserException,
IOException, ParserException {
XmlPullParser xpp = xmlParserFactory.newPullParser();
xpp.setInput(inputStream, inputEncoding);
int eventType = xpp.next();
if (eventType != XmlPullParser.START_TAG || !"MPD".equals(xpp.getName())) {
throw new ParserException(
"inputStream does not contain a valid media presentation description");
}
return parseMediaPresentationDescription(xpp, contentId);
return parseMediaPresentationDescription(xpp, contentId, baseUrl);
}

private MediaPresentationDescription parseMediaPresentationDescription(XmlPullParser xpp,
String contentId) throws XmlPullParserException, IOException {
String contentId, Uri parentBaseUrl) throws XmlPullParserException, IOException {
Uri baseUrl = parentBaseUrl;
long duration = parseDurationMs(xpp, "mediaPresentationDuration");
long minBufferTime = parseDurationMs(xpp, "minBufferTime");
String typeString = xpp.getAttributeValue(null, "type");
Expand All @@ -93,17 +95,20 @@ private MediaPresentationDescription parseMediaPresentationDescription(XmlPullPa
List<Period> periods = new ArrayList<Period>();
do {
xpp.next();
if (isStartTag(xpp, "Period")) {
periods.add(parsePeriod(xpp, contentId, duration));
if (isStartTag(xpp, "BaseURL")) {
baseUrl = parseBaseUrl(xpp, parentBaseUrl);
} else if (isStartTag(xpp, "Period")) {
periods.add(parsePeriod(xpp, contentId, baseUrl, duration));
}
} while (!isEndTag(xpp, "MPD"));

return new MediaPresentationDescription(duration, minBufferTime, dynamic, minUpdateTime,
periods);
}

private Period parsePeriod(XmlPullParser xpp, String contentId, long mediaPresentationDuration)
throws XmlPullParserException, IOException {
private Period parsePeriod(XmlPullParser xpp, String contentId, Uri parentBaseUrl,
long mediaPresentationDuration) throws XmlPullParserException, IOException {
Uri baseUrl = parentBaseUrl;
int id = parseInt(xpp, "id");
long start = parseDurationMs(xpp, "start", 0);
long duration = parseDurationMs(xpp, "duration", mediaPresentationDuration);
Expand All @@ -115,8 +120,10 @@ private Period parsePeriod(XmlPullParser xpp, String contentId, long mediaPresen
long presentationTimeOffset = 0;
do {
xpp.next();
if (isStartTag(xpp, "AdaptationSet")) {
adaptationSets.add(parseAdaptationSet(xpp, contentId, start, duration,
if (isStartTag(xpp, "BaseURL")) {
baseUrl = parseBaseUrl(xpp, parentBaseUrl);
} else if (isStartTag(xpp, "AdaptationSet")) {
adaptationSets.add(parseAdaptationSet(xpp, contentId, baseUrl, start, duration,
segmentTimelineList));
} else if (isStartTag(xpp, "SegmentList")) {
segmentStartNumber = parseInt(xpp, "startNumber");
Expand Down Expand Up @@ -151,9 +158,10 @@ private List<Segment.Timeline> parsePeriodSegmentList(
return segmentTimelineList;
}

private AdaptationSet parseAdaptationSet(XmlPullParser xpp, String contentId, long periodStart,
long periodDuration, List<Segment.Timeline> segmentTimelineList)
private AdaptationSet parseAdaptationSet(XmlPullParser xpp, String contentId, Uri parentBaseUrl,
long periodStart, long periodDuration, List<Segment.Timeline> segmentTimelineList)
throws XmlPullParserException, IOException {
Uri baseUrl = parentBaseUrl;
int id = -1;
int contentType = AdaptationSet.TYPE_UNKNOWN;

Expand All @@ -175,7 +183,9 @@ private AdaptationSet parseAdaptationSet(XmlPullParser xpp, String contentId, lo
do {
xpp.next();
if (contentType != AdaptationSet.TYPE_UNKNOWN) {
if (isStartTag(xpp, "ContentProtection")) {
if (isStartTag(xpp, "BaseURL")) {
baseUrl = parseBaseUrl(xpp, parentBaseUrl);
} else if (isStartTag(xpp, "ContentProtection")) {
if (contentProtections == null) {
contentProtections = new ArrayList<ContentProtection>();
}
Expand All @@ -187,8 +197,8 @@ private AdaptationSet parseAdaptationSet(XmlPullParser xpp, String contentId, lo
: "audio".equals(contentTypeString) ? AdaptationSet.TYPE_AUDIO
: AdaptationSet.TYPE_UNKNOWN;
} else if (isStartTag(xpp, "Representation")) {
representations.add(parseRepresentation(xpp, contentId, periodStart, periodDuration,
mimeType, segmentTimelineList));
representations.add(parseRepresentation(xpp, contentId, baseUrl, periodStart,
periodDuration, mimeType, segmentTimelineList));
}
}
} while (!isEndTag(xpp, "AdaptationSet"));
Expand All @@ -208,9 +218,10 @@ protected ContentProtection parseContentProtection(XmlPullParser xpp)
return new ContentProtection(schemeUriId, null);
}

private Representation parseRepresentation(XmlPullParser xpp, String contentId, long periodStart,
long periodDuration, String parentMimeType, List<Segment.Timeline> segmentTimelineList)
throws XmlPullParserException, IOException {
private Representation parseRepresentation(XmlPullParser xpp, String contentId, Uri parentBaseUrl,
long periodStart, long periodDuration, String parentMimeType,
List<Segment.Timeline> segmentTimelineList) throws XmlPullParserException, IOException {
Uri baseUrl = parentBaseUrl;
String id = xpp.getAttributeValue(null, "id");
int bandwidth = parseInt(xpp, "bandwidth") / 8;
int audioSamplingRate = parseInt(xpp, "audioSamplingRate");
Expand All @@ -222,7 +233,6 @@ private Representation parseRepresentation(XmlPullParser xpp, String contentId,
mimeType = parentMimeType;
}

String representationUrl = null;
long indexStart = -1;
long indexEnd = -1;
long initializationStart = -1;
Expand All @@ -232,8 +242,7 @@ private Representation parseRepresentation(XmlPullParser xpp, String contentId,
do {
xpp.next();
if (isStartTag(xpp, "BaseURL")) {
xpp.next();
representationUrl = xpp.getText();
baseUrl = parseBaseUrl(xpp, parentBaseUrl);
} else if (isStartTag(xpp, "AudioChannelConfiguration")) {
numChannels = Integer.parseInt(xpp.getAttributeValue(null, "value"));
} else if (isStartTag(xpp, "SegmentBase")) {
Expand All @@ -249,15 +258,14 @@ private Representation parseRepresentation(XmlPullParser xpp, String contentId,
}
} while (!isEndTag(xpp, "Representation"));

Uri uri = Uri.parse(representationUrl);
Format format = new Format(id, mimeType, width, height, numChannels, audioSamplingRate,
bandwidth);
if (segmentList == null) {
return new Representation(contentId, -1, format, uri, DataSpec.LENGTH_UNBOUNDED,
return new Representation(contentId, -1, format, baseUrl, DataSpec.LENGTH_UNBOUNDED,
initializationStart, initializationEnd, indexStart, indexEnd, periodStart,
periodDuration);
} else {
return new SegmentedRepresentation(contentId, format, uri, initializationStart,
return new SegmentedRepresentation(contentId, format, baseUrl, initializationStart,
initializationEnd, indexStart, indexEnd, periodStart, periodDuration, segmentList);
}
}
Expand Down Expand Up @@ -321,7 +329,7 @@ private long parseDurationMs(XmlPullParser xpp, String name) {
return parseDurationMs(xpp, name, -1);
}

private long parseDurationMs(XmlPullParser xpp, String name, long defaultValue) {
private static long parseDurationMs(XmlPullParser xpp, String name, long defaultValue) {
String value = xpp.getAttributeValue(null, name);
if (value != null) {
Matcher matcher = DURATION.matcher(value);
Expand All @@ -340,4 +348,16 @@ private long parseDurationMs(XmlPullParser xpp, String name, long defaultValue)
return defaultValue;
}

private static Uri parseBaseUrl(XmlPullParser xpp, Uri parentBaseUrl)
throws XmlPullParserException, IOException {
xpp.next();
String newBaseUrlText = xpp.getText();
Uri newBaseUri = Uri.parse(newBaseUrlText);
if (newBaseUri.isAbsolute()) {
return newBaseUri;
} else {
return parentBaseUrl.buildUpon().appendEncodedPath(newBaseUrlText).build();
}
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,8 @@
import com.google.android.exoplayer.ParserException;
import com.google.android.exoplayer.util.ManifestFetcher;

import android.net.Uri;

import org.xmlpull.v1.XmlPullParserException;

import java.io.IOException;
Expand Down Expand Up @@ -56,7 +58,7 @@ public SmoothStreamingManifestFetcher(ManifestCallback<SmoothStreamingManifest>

@Override
protected SmoothStreamingManifest parse(InputStream stream, String inputEncoding,
String contentId) throws IOException, ParserException {
String contentId, Uri baseUrl) throws IOException, ParserException {
try {
return parser.parse(stream, inputEncoding);
} catch (XmlPullParserException e) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@

import com.google.android.exoplayer.ParserException;

import android.net.Uri;
import android.os.AsyncTask;

import java.io.IOException;
Expand Down Expand Up @@ -84,14 +85,15 @@ public ManifestFetcher(ManifestCallback<T> callback, int timeoutMillis) {
protected final T doInBackground(String... data) {
try {
contentId = data.length > 1 ? data[1] : null;
URL url = new URL(data[0]);
String urlString = data[0];
String inputEncoding = null;
InputStream inputStream = null;
try {
HttpURLConnection connection = configureHttpConnection(url);
Uri baseUrl = Util.parseBaseUri(urlString);
HttpURLConnection connection = configureHttpConnection(new URL(urlString));
inputStream = connection.getInputStream();
inputEncoding = connection.getContentEncoding();
return parse(inputStream, inputEncoding, contentId);
return parse(inputStream, inputEncoding, contentId, baseUrl);
} finally {
if (inputStream != null) {
inputStream.close();
Expand Down Expand Up @@ -119,11 +121,13 @@ protected final void onPostExecute(T manifest) {
* @param stream The input stream to read.
* @param inputEncoding The encoding of the input stream.
* @param contentId The content id of the media.
* @param baseUrl Required where the manifest contains urls that are relative to a base url. May
* be null where this is not the case.
* @throws IOException If an error occurred loading the data.
* @throws ParserException If an error occurred parsing the loaded data.
*/
protected abstract T parse(InputStream stream, String inputEncoding, String contentId) throws
IOException, ParserException;
protected abstract T parse(InputStream stream, String inputEncoding, String contentId,
Uri baseUrl) throws IOException, ParserException;

private HttpURLConnection configureHttpConnection(URL url) throws IOException {
HttpURLConnection connection = (HttpURLConnection) url.openConnection();
Expand Down
13 changes: 13 additions & 0 deletions library/src/main/java/com/google/android/exoplayer/util/Util.java
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,8 @@

import com.google.android.exoplayer.upstream.DataSource;

import android.net.Uri;

import java.io.IOException;
import java.net.URL;
import java.util.Arrays;
Expand Down Expand Up @@ -115,6 +117,17 @@ public static String toLowerInvariant(String text) {
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('/')));
}

/**
* Returns the index of the largest value in an array that is less than (or optionally equal to)
* a specified key.
Expand Down

0 comments on commit 9e16dec

Please sign in to comment.