More flexible mimeType handling in mpd parser.

- Allow the content type of an adaptation set to be inferred
from the mimeTypes of the contained representations.
- Ensure the contained mimeTypes are consistent with one
another, and with the adaptation set.

Ref: Issue #2
This commit is contained in:
Oliver Woodman 2014-07-10 12:01:12 +01:00
parent 686ac2a6f5
commit 16fe6a809e
2 changed files with 110 additions and 39 deletions

View File

@ -18,9 +18,11 @@ package com.google.android.exoplayer.dash.mpd;
import com.google.android.exoplayer.ParserException; import com.google.android.exoplayer.ParserException;
import com.google.android.exoplayer.chunk.Format; import com.google.android.exoplayer.chunk.Format;
import com.google.android.exoplayer.upstream.DataSpec; import com.google.android.exoplayer.upstream.DataSpec;
import com.google.android.exoplayer.util.Assertions;
import com.google.android.exoplayer.util.MimeTypes; import com.google.android.exoplayer.util.MimeTypes;
import android.net.Uri; import android.net.Uri;
import android.text.TextUtils;
import org.xml.sax.helpers.DefaultHandler; import org.xml.sax.helpers.DefaultHandler;
import org.xmlpull.v1.XmlPullParser; import org.xmlpull.v1.XmlPullParser;
@ -163,26 +165,15 @@ public class MediaPresentationDescriptionParser extends DefaultHandler {
throws XmlPullParserException, IOException { throws XmlPullParserException, IOException {
Uri baseUrl = parentBaseUrl; Uri baseUrl = parentBaseUrl;
int id = -1; int id = -1;
int contentType = AdaptationSet.TYPE_UNKNOWN;
// TODO: Correctly handle other common attributes and elements. See 23009-1 Table 9. // TODO: Correctly handle other common attributes and elements. See 23009-1 Table 9.
String mimeType = xpp.getAttributeValue(null, "mimeType"); String mimeType = xpp.getAttributeValue(null, "mimeType");
if (mimeType != null) { int contentType = parseAdaptationSetTypeFromMimeType(mimeType);
if (MimeTypes.isAudio(mimeType)) {
contentType = AdaptationSet.TYPE_AUDIO;
} else if (MimeTypes.isVideo(mimeType)) {
contentType = AdaptationSet.TYPE_VIDEO;
} else if (MimeTypes.isText(mimeType)
|| mimeType.equalsIgnoreCase(MimeTypes.APPLICATION_TTML)) {
contentType = AdaptationSet.TYPE_TEXT;
}
}
List<ContentProtection> contentProtections = null; List<ContentProtection> contentProtections = null;
List<Representation> representations = new ArrayList<Representation>(); List<Representation> representations = new ArrayList<Representation>();
do { do {
xpp.next(); xpp.next();
if (contentType != AdaptationSet.TYPE_UNKNOWN) {
if (isStartTag(xpp, "BaseURL")) { if (isStartTag(xpp, "BaseURL")) {
baseUrl = parseBaseUrl(xpp, parentBaseUrl); baseUrl = parseBaseUrl(xpp, parentBaseUrl);
} else if (isStartTag(xpp, "ContentProtection")) { } else if (isStartTag(xpp, "ContentProtection")) {
@ -192,14 +183,14 @@ public class MediaPresentationDescriptionParser extends DefaultHandler {
contentProtections.add(parseContentProtection(xpp)); contentProtections.add(parseContentProtection(xpp));
} else if (isStartTag(xpp, "ContentComponent")) { } else if (isStartTag(xpp, "ContentComponent")) {
id = Integer.parseInt(xpp.getAttributeValue(null, "id")); id = Integer.parseInt(xpp.getAttributeValue(null, "id"));
String contentTypeString = xpp.getAttributeValue(null, "contentType"); contentType = checkAdaptationSetTypeConsistency(contentType,
contentType = "video".equals(contentTypeString) ? AdaptationSet.TYPE_VIDEO parseAdaptationSetType(xpp.getAttributeValue(null, "contentType")));
: "audio".equals(contentTypeString) ? AdaptationSet.TYPE_AUDIO
: AdaptationSet.TYPE_UNKNOWN;
} else if (isStartTag(xpp, "Representation")) { } else if (isStartTag(xpp, "Representation")) {
representations.add(parseRepresentation(xpp, contentId, baseUrl, periodStart, Representation representation = parseRepresentation(xpp, contentId, baseUrl, periodStart,
periodDuration, mimeType, segmentTimelineList)); periodDuration, mimeType, segmentTimelineList);
} contentType = checkAdaptationSetTypeConsistency(contentType,
parseAdaptationSetTypeFromMimeType(representation.format.mimeType));
representations.add(representation);
} }
} while (!isEndTag(xpp, "AdaptationSet")); } while (!isEndTag(xpp, "AdaptationSet"));
@ -360,4 +351,42 @@ public class MediaPresentationDescriptionParser extends DefaultHandler {
} }
} }
private static int parseAdaptationSetType(String contentType) {
return TextUtils.isEmpty(contentType) ? AdaptationSet.TYPE_UNKNOWN
: MimeTypes.BASE_TYPE_AUDIO.equals(contentType) ? AdaptationSet.TYPE_AUDIO
: MimeTypes.BASE_TYPE_VIDEO.equals(contentType) ? AdaptationSet.TYPE_VIDEO
: MimeTypes.BASE_TYPE_TEXT.equals(contentType) ? AdaptationSet.TYPE_TEXT
: AdaptationSet.TYPE_UNKNOWN;
}
private static int parseAdaptationSetTypeFromMimeType(String mimeType) {
return TextUtils.isEmpty(mimeType) ? AdaptationSet.TYPE_UNKNOWN
: MimeTypes.isAudio(mimeType) ? AdaptationSet.TYPE_AUDIO
: MimeTypes.isVideo(mimeType) ? AdaptationSet.TYPE_VIDEO
: MimeTypes.isText(mimeType) || MimeTypes.isTtml(mimeType) ? AdaptationSet.TYPE_TEXT
: AdaptationSet.TYPE_UNKNOWN;
}
/**
* Checks two adaptation set types for consistency, returning the consistent type, or throwing an
* {@link IllegalStateException} if the types are inconsistent.
* <p>
* Two types are consistent if they are equal, or if one is {@link AdaptationSet#TYPE_UNKNOWN}.
* Where one of the types is {@link AdaptationSet#TYPE_UNKNOWN}, the other is returned.
*
* @param firstType The first type.
* @param secondType The second type.
* @return The consistent type.
*/
private static int checkAdaptationSetTypeConsistency(int firstType, int secondType) {
if (firstType == AdaptationSet.TYPE_UNKNOWN) {
return secondType;
} else if (secondType == AdaptationSet.TYPE_UNKNOWN) {
return firstType;
} else {
Assertions.checkState(firstType == secondType);
return firstType;
}
}
} }

View File

@ -20,17 +20,39 @@ package com.google.android.exoplayer.util;
*/ */
public class MimeTypes { public class MimeTypes {
public static final String VIDEO_MP4 = "video/mp4"; public static final String BASE_TYPE_VIDEO = "video";
public static final String VIDEO_WEBM = "video/webm"; public static final String BASE_TYPE_AUDIO = "audio";
public static final String VIDEO_H264 = "video/avc"; public static final String BASE_TYPE_TEXT = "text";
public static final String VIDEO_VP9 = "video/x-vnd.on2.vp9"; public static final String BASE_TYPE_APPLICATION = "application";
public static final String AUDIO_MP4 = "audio/mp4";
public static final String AUDIO_AAC = "audio/mp4a-latm"; public static final String VIDEO_MP4 = BASE_TYPE_VIDEO + "/mp4";
public static final String TEXT_VTT = "text/vtt"; public static final String VIDEO_WEBM = BASE_TYPE_VIDEO + "/webm";
public static final String APPLICATION_TTML = "application/ttml+xml"; public static final String VIDEO_H264 = BASE_TYPE_VIDEO + "/avc";
public static final String VIDEO_VP9 = BASE_TYPE_VIDEO + "/x-vnd.on2.vp9";
public static final String AUDIO_MP4 = BASE_TYPE_AUDIO + "/mp4";
public static final String AUDIO_AAC = BASE_TYPE_AUDIO + "/mp4a-latm";
public static final String TEXT_VTT = BASE_TYPE_TEXT + "/vtt";
public static final String APPLICATION_TTML = BASE_TYPE_APPLICATION + "/ttml+xml";
private MimeTypes() {} private MimeTypes() {}
/**
* Returns the top-level type of {@code mimeType}.
*
* @param mimeType The mimeType whose top-level type is required.
* @return The top-level type.
*/
public static String getTopLevelType(String mimeType) {
int indexOfSlash = mimeType.indexOf('/');
if (indexOfSlash == -1) {
throw new IllegalArgumentException("Invalid mime type: " + mimeType);
}
return mimeType.substring(0, indexOfSlash);
}
/** /**
* Whether the top-level type of {@code mimeType} is audio. * Whether the top-level type of {@code mimeType} is audio.
* *
@ -38,7 +60,7 @@ public class MimeTypes {
* @return Whether the top level type is audio. * @return Whether the top level type is audio.
*/ */
public static boolean isAudio(String mimeType) { public static boolean isAudio(String mimeType) {
return mimeType.startsWith("audio/"); return getTopLevelType(mimeType).equals(BASE_TYPE_AUDIO);
} }
/** /**
@ -48,7 +70,7 @@ public class MimeTypes {
* @return Whether the top level type is video. * @return Whether the top level type is video.
*/ */
public static boolean isVideo(String mimeType) { public static boolean isVideo(String mimeType) {
return mimeType.startsWith("video/"); return getTopLevelType(mimeType).equals(BASE_TYPE_VIDEO);
} }
/** /**
@ -58,7 +80,27 @@ public class MimeTypes {
* @return Whether the top level type is text. * @return Whether the top level type is text.
*/ */
public static boolean isText(String mimeType) { public static boolean isText(String mimeType) {
return mimeType.startsWith("text/"); return getTopLevelType(mimeType).equals(BASE_TYPE_TEXT);
}
/**
* Whether the top-level type of {@code mimeType} is application.
*
* @param mimeType The mimeType to test.
* @return Whether the top level type is application.
*/
public static boolean isApplication(String mimeType) {
return getTopLevelType(mimeType).equals(BASE_TYPE_APPLICATION);
}
/**
* Whether the mimeType is {@link #APPLICATION_TTML}.
*
* @param mimeType The mimeType to test.
* @return Whether the mimeType is {@link #APPLICATION_TTML}.
*/
public static boolean isTtml(String mimeType) {
return mimeType.equals(APPLICATION_TTML);
} }
} }