From 16fe6a809e97b4b348cb542a095faf49e7cee0fc Mon Sep 17 00:00:00 2001 From: Oliver Woodman Date: Thu, 10 Jul 2014 12:01:12 +0100 Subject: [PATCH] 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 --- .../MediaPresentationDescriptionParser.java | 85 +++++++++++++------ .../android/exoplayer/util/MimeTypes.java | 64 +++++++++++--- 2 files changed, 110 insertions(+), 39 deletions(-) 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 18a00f99ac7..9f8e1e2c0c0 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 @@ -18,9 +18,11 @@ import com.google.android.exoplayer.ParserException; import com.google.android.exoplayer.chunk.Format; import com.google.android.exoplayer.upstream.DataSpec; +import com.google.android.exoplayer.util.Assertions; import com.google.android.exoplayer.util.MimeTypes; import android.net.Uri; +import android.text.TextUtils; import org.xml.sax.helpers.DefaultHandler; import org.xmlpull.v1.XmlPullParser; @@ -163,43 +165,32 @@ private AdaptationSet parseAdaptationSet(XmlPullParser xpp, String contentId, Ur throws XmlPullParserException, IOException { Uri baseUrl = parentBaseUrl; int id = -1; - int contentType = AdaptationSet.TYPE_UNKNOWN; // TODO: Correctly handle other common attributes and elements. See 23009-1 Table 9. String mimeType = xpp.getAttributeValue(null, "mimeType"); - if (mimeType != null) { - 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; - } - } + int contentType = parseAdaptationSetTypeFromMimeType(mimeType); List contentProtections = null; List representations = new ArrayList(); do { xpp.next(); - if (contentType != AdaptationSet.TYPE_UNKNOWN) { - if (isStartTag(xpp, "BaseURL")) { - baseUrl = parseBaseUrl(xpp, parentBaseUrl); - } else if (isStartTag(xpp, "ContentProtection")) { - if (contentProtections == null) { - contentProtections = new ArrayList(); - } - contentProtections.add(parseContentProtection(xpp)); - } else if (isStartTag(xpp, "ContentComponent")) { - id = Integer.parseInt(xpp.getAttributeValue(null, "id")); - String contentTypeString = xpp.getAttributeValue(null, "contentType"); - contentType = "video".equals(contentTypeString) ? AdaptationSet.TYPE_VIDEO - : "audio".equals(contentTypeString) ? AdaptationSet.TYPE_AUDIO - : AdaptationSet.TYPE_UNKNOWN; - } else if (isStartTag(xpp, "Representation")) { - representations.add(parseRepresentation(xpp, contentId, baseUrl, periodStart, - periodDuration, mimeType, segmentTimelineList)); + if (isStartTag(xpp, "BaseURL")) { + baseUrl = parseBaseUrl(xpp, parentBaseUrl); + } else if (isStartTag(xpp, "ContentProtection")) { + if (contentProtections == null) { + contentProtections = new ArrayList(); } + contentProtections.add(parseContentProtection(xpp)); + } else if (isStartTag(xpp, "ContentComponent")) { + id = Integer.parseInt(xpp.getAttributeValue(null, "id")); + contentType = checkAdaptationSetTypeConsistency(contentType, + parseAdaptationSetType(xpp.getAttributeValue(null, "contentType"))); + } else if (isStartTag(xpp, "Representation")) { + Representation representation = parseRepresentation(xpp, contentId, baseUrl, periodStart, + periodDuration, mimeType, segmentTimelineList); + contentType = checkAdaptationSetTypeConsistency(contentType, + parseAdaptationSetTypeFromMimeType(representation.format.mimeType)); + representations.add(representation); } } while (!isEndTag(xpp, "AdaptationSet")); @@ -360,4 +351,42 @@ private static Uri parseBaseUrl(XmlPullParser xpp, Uri parentBaseUrl) } } + 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. + *

+ * 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; + } + } + } diff --git a/library/src/main/java/com/google/android/exoplayer/util/MimeTypes.java b/library/src/main/java/com/google/android/exoplayer/util/MimeTypes.java index a9ae0aa5c5e..63d5220ac11 100644 --- a/library/src/main/java/com/google/android/exoplayer/util/MimeTypes.java +++ b/library/src/main/java/com/google/android/exoplayer/util/MimeTypes.java @@ -20,17 +20,39 @@ */ public class MimeTypes { - public static final String VIDEO_MP4 = "video/mp4"; - public static final String VIDEO_WEBM = "video/webm"; - public static final String VIDEO_H264 = "video/avc"; - public static final String VIDEO_VP9 = "video/x-vnd.on2.vp9"; - public static final String AUDIO_MP4 = "audio/mp4"; - public static final String AUDIO_AAC = "audio/mp4a-latm"; - public static final String TEXT_VTT = "text/vtt"; - public static final String APPLICATION_TTML = "application/ttml+xml"; + public static final String BASE_TYPE_VIDEO = "video"; + public static final String BASE_TYPE_AUDIO = "audio"; + public static final String BASE_TYPE_TEXT = "text"; + public static final String BASE_TYPE_APPLICATION = "application"; + + public static final String VIDEO_MP4 = BASE_TYPE_VIDEO + "/mp4"; + public static final String VIDEO_WEBM = BASE_TYPE_VIDEO + "/webm"; + 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() {} + /** + * 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. * @@ -38,7 +60,7 @@ private MimeTypes() {} * @return Whether the top level type is audio. */ public static boolean isAudio(String mimeType) { - return mimeType.startsWith("audio/"); + return getTopLevelType(mimeType).equals(BASE_TYPE_AUDIO); } /** @@ -48,7 +70,7 @@ public static boolean isAudio(String mimeType) { * @return Whether the top level type is video. */ public static boolean isVideo(String mimeType) { - return mimeType.startsWith("video/"); + return getTopLevelType(mimeType).equals(BASE_TYPE_VIDEO); } /** @@ -58,7 +80,27 @@ public static boolean isVideo(String mimeType) { * @return Whether the top level type is text. */ 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); } }