Enable ContentProtect elements at the Representation level

This commit is contained in:
Oliver Woodman 2015-03-31 11:28:51 +01:00
parent 5a5935cb72
commit acd1b9acff
2 changed files with 161 additions and 9 deletions

View File

@ -15,6 +15,10 @@
*/ */
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.Util;
import java.util.Arrays;
import java.util.UUID; import java.util.UUID;
/** /**
@ -43,9 +47,38 @@ public class ContentProtection {
* @param data Protection scheme specific initialization data. May be null. * @param data Protection scheme specific initialization data. May be null.
*/ */
public ContentProtection(String schemeUriId, UUID uuid, byte[] data) { public ContentProtection(String schemeUriId, UUID uuid, byte[] data) {
this.schemeUriId = schemeUriId; this.schemeUriId = Assertions.checkNotNull(schemeUriId);
this.uuid = uuid; this.uuid = uuid;
this.data = data; this.data = data;
} }
@Override
public boolean equals(Object obj) {
if (!(obj instanceof ContentProtection)) {
return false;
}
if (obj == this) {
return true;
}
ContentProtection other = (ContentProtection) obj;
return schemeUriId.equals(other.schemeUriId)
&& Util.areEqual(uuid, other.uuid)
&& Arrays.equals(data, other.data);
}
@Override
public int hashCode() {
int hashCode = 1;
hashCode = hashCode * 37 + schemeUriId.hashCode();
if (uuid != null) {
hashCode = hashCode * 37 + uuid.hashCode();
}
if (data != null) {
hashCode = hashCode * 37 + Arrays.hashCode(data);
}
return hashCode;
}
} }

View File

@ -38,6 +38,8 @@ import java.io.IOException;
import java.io.InputStream; import java.io.InputStream;
import java.text.ParseException; import java.text.ParseException;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.List; import java.util.List;
/** /**
@ -178,24 +180,22 @@ public class MediaPresentationDescriptionParser extends DefaultHandler
int contentType = parseAdaptationSetTypeFromMimeType(mimeType); int contentType = parseAdaptationSetTypeFromMimeType(mimeType);
int id = -1; int id = -1;
List<ContentProtection> contentProtections = null; ContentProtectionsBuilder contentProtectionsBuilder = new ContentProtectionsBuilder();
List<Representation> representations = new ArrayList<Representation>(); List<Representation> representations = new ArrayList<Representation>();
do { do {
xpp.next(); xpp.next();
if (isStartTag(xpp, "BaseURL")) { if (isStartTag(xpp, "BaseURL")) {
baseUrl = parseBaseUrl(xpp, baseUrl); baseUrl = parseBaseUrl(xpp, baseUrl);
} else if (isStartTag(xpp, "ContentProtection")) { } else if (isStartTag(xpp, "ContentProtection")) {
if (contentProtections == null) { contentProtectionsBuilder.addAdaptationSetProtection(parseContentProtection(xpp));
contentProtections = new ArrayList<ContentProtection>();
}
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"));
contentType = checkAdaptationSetTypeConsistency(contentType, contentType = checkAdaptationSetTypeConsistency(contentType,
parseAdaptationSetType(xpp.getAttributeValue(null, "contentType"))); parseAdaptationSetType(xpp.getAttributeValue(null, "contentType")));
} else if (isStartTag(xpp, "Representation")) { } else if (isStartTag(xpp, "Representation")) {
Representation representation = parseRepresentation(xpp, baseUrl, periodStartMs, Representation representation = parseRepresentation(xpp, baseUrl, periodStartMs,
periodDurationMs, mimeType, language, segmentBase); periodDurationMs, mimeType, language, segmentBase, contentProtectionsBuilder);
contentProtectionsBuilder.endRepresentation();
contentType = checkAdaptationSetTypeConsistency(contentType, contentType = checkAdaptationSetTypeConsistency(contentType,
parseAdaptationSetTypeFromMimeType(representation.format.mimeType)); parseAdaptationSetTypeFromMimeType(representation.format.mimeType));
representations.add(representation); representations.add(representation);
@ -211,7 +211,7 @@ public class MediaPresentationDescriptionParser extends DefaultHandler
} }
} while (!isEndTag(xpp, "AdaptationSet")); } while (!isEndTag(xpp, "AdaptationSet"));
return buildAdaptationSet(id, contentType, representations, contentProtections); return buildAdaptationSet(id, contentType, representations, contentProtectionsBuilder.build());
} }
protected AdaptationSet buildAdaptationSet(int id, int contentType, protected AdaptationSet buildAdaptationSet(int id, int contentType,
@ -289,7 +289,8 @@ public class MediaPresentationDescriptionParser extends DefaultHandler
protected Representation parseRepresentation(XmlPullParser xpp, String baseUrl, protected Representation parseRepresentation(XmlPullParser xpp, String baseUrl,
long periodStartMs, long periodDurationMs, String mimeType, String language, long periodStartMs, long periodDurationMs, String mimeType, String language,
SegmentBase segmentBase) throws XmlPullParserException, IOException { SegmentBase segmentBase, ContentProtectionsBuilder contentProtectionsBuilder)
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");
@ -312,6 +313,8 @@ public class MediaPresentationDescriptionParser extends DefaultHandler
} else if (isStartTag(xpp, "SegmentTemplate")) { } else if (isStartTag(xpp, "SegmentTemplate")) {
segmentBase = parseSegmentTemplate(xpp, baseUrl, (SegmentTemplate) segmentBase, segmentBase = parseSegmentTemplate(xpp, baseUrl, (SegmentTemplate) segmentBase,
periodDurationMs); periodDurationMs);
} else if (isStartTag(xpp, "ContentProtection")) {
contentProtectionsBuilder.addRepresentationProtection(parseContentProtection(xpp));
} }
} while (!isEndTag(xpp, "Representation")); } while (!isEndTag(xpp, "Representation"));
@ -577,4 +580,120 @@ public class MediaPresentationDescriptionParser extends DefaultHandler
return value == null ? defaultValue : value; return value == null ? defaultValue : value;
} }
/**
* Builds a list of {@link ContentProtection} elements for an {@link AdaptationSet}.
* <p>
* If child Representation elements contain ContentProtection elements, then it is required that
* they all define the same ones. If they do, the ContentProtection elements are bubbled up to the
* AdaptationSet. Child Representation elements defining different ContentProtection elements is
* considered an error.
*/
protected static final class ContentProtectionsBuilder implements Comparator<ContentProtection> {
private ArrayList<ContentProtection> adaptationSetProtections;
private ArrayList<ContentProtection> representationProtections;
private ArrayList<ContentProtection> currentRepresentationProtections;
private boolean representationProtectionsSet;
/**
* Adds a {@link ContentProtection} found in the AdaptationSet element.
*
* @param contentProtection The {@link ContentProtection} to add.
*/
public void addAdaptationSetProtection(ContentProtection contentProtection) {
if (adaptationSetProtections == null) {
adaptationSetProtections = new ArrayList<ContentProtection>();
}
maybeAddContentProtection(adaptationSetProtections, contentProtection);
}
/**
* Adds a {@link ContentProtection} found in a child Representation element.
*
* @param contentProtection The {@link ContentProtection} to add.
*/
public void addRepresentationProtection(ContentProtection contentProtection) {
if (currentRepresentationProtections == null) {
currentRepresentationProtections = new ArrayList<ContentProtection>();
}
maybeAddContentProtection(currentRepresentationProtections, contentProtection);
}
/**
* Should be invoked after processing each child Representation element, in order to apply
* consistency checks.
*/
public void endRepresentation() {
if (!representationProtectionsSet) {
if (currentRepresentationProtections != null) {
Collections.sort(currentRepresentationProtections, this);
}
representationProtections = currentRepresentationProtections;
representationProtectionsSet = true;
} else {
// Assert that each Representation element defines the same ContentProtection elements.
if (currentRepresentationProtections == null) {
Assertions.checkState(representationProtections == null);
} else {
Collections.sort(currentRepresentationProtections, this);
Assertions.checkState(currentRepresentationProtections.equals(representationProtections));
}
}
currentRepresentationProtections = null;
}
/**
* Returns the final list of consistent {@link ContentProtection} elements.
*/
public ArrayList<ContentProtection> build() {
if (adaptationSetProtections == null) {
return representationProtections;
} else if (representationProtections == null) {
return adaptationSetProtections;
} else {
// Bubble up ContentProtection elements found in the child Representation elements.
for (int i = 0; i < representationProtections.size(); i++) {
maybeAddContentProtection(adaptationSetProtections, representationProtections.get(i));
}
return adaptationSetProtections;
}
}
/**
* Checks a ContentProtection for consistency with the given list, adding it if necessary.
* <ul>
* <li>If the new ContentProtection matches another in the list, it's consistent and is not
* added to the list.
* <li>If the new ContentProtection has the same schemeUriId as another ContentProtection in the
* list, but its other attributes do not match, then it's inconsistent and an
* {@link IllegalStateException} is thrown.
* <li>Else the new ContentProtection has a unique schemeUriId, it's consistent and is added.
* </ul>
*
* @param contentProtections The list of ContentProtection elements currently known.
* @param contentProtection The ContentProtection to add.
*/
private void maybeAddContentProtection(List<ContentProtection> contentProtections,
ContentProtection contentProtection) {
if (!contentProtections.contains(contentProtection)) {
for (int i = 0; i < contentProtections.size(); i++) {
// If contains returned false (no complete match), but find a matching schemeUriId, then
// the MPD contains inconsistent ContentProtection data.
Assertions.checkState(
!contentProtections.get(i).schemeUriId.equals(contentProtection.schemeUriId));
}
contentProtections.add(contentProtection);
}
}
// Comparator implementation.
@Override
public int compare(ContentProtection first, ContentProtection second) {
return first.schemeUriId.compareTo(second.schemeUriId);
}
}
} }