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;
import com.google.android.exoplayer.util.Assertions;
import com.google.android.exoplayer.util.Util;
import java.util.Arrays;
import java.util.UUID;
/**
@ -43,9 +47,38 @@ public class ContentProtection {
* @param data Protection scheme specific initialization data. May be null.
*/
public ContentProtection(String schemeUriId, UUID uuid, byte[] data) {
this.schemeUriId = schemeUriId;
this.schemeUriId = Assertions.checkNotNull(schemeUriId);
this.uuid = uuid;
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.text.ParseException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.List;
/**
@ -178,24 +180,22 @@ public class MediaPresentationDescriptionParser extends DefaultHandler
int contentType = parseAdaptationSetTypeFromMimeType(mimeType);
int id = -1;
List<ContentProtection> contentProtections = null;
ContentProtectionsBuilder contentProtectionsBuilder = new ContentProtectionsBuilder();
List<Representation> representations = new ArrayList<Representation>();
do {
xpp.next();
if (isStartTag(xpp, "BaseURL")) {
baseUrl = parseBaseUrl(xpp, baseUrl);
} else if (isStartTag(xpp, "ContentProtection")) {
if (contentProtections == null) {
contentProtections = new ArrayList<ContentProtection>();
}
contentProtections.add(parseContentProtection(xpp));
contentProtectionsBuilder.addAdaptationSetProtection(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, baseUrl, periodStartMs,
periodDurationMs, mimeType, language, segmentBase);
periodDurationMs, mimeType, language, segmentBase, contentProtectionsBuilder);
contentProtectionsBuilder.endRepresentation();
contentType = checkAdaptationSetTypeConsistency(contentType,
parseAdaptationSetTypeFromMimeType(representation.format.mimeType));
representations.add(representation);
@ -211,7 +211,7 @@ public class MediaPresentationDescriptionParser extends DefaultHandler
}
} while (!isEndTag(xpp, "AdaptationSet"));
return buildAdaptationSet(id, contentType, representations, contentProtections);
return buildAdaptationSet(id, contentType, representations, contentProtectionsBuilder.build());
}
protected AdaptationSet buildAdaptationSet(int id, int contentType,
@ -289,7 +289,8 @@ public class MediaPresentationDescriptionParser extends DefaultHandler
protected Representation parseRepresentation(XmlPullParser xpp, String baseUrl,
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");
int bandwidth = parseInt(xpp, "bandwidth");
int audioSamplingRate = parseInt(xpp, "audioSamplingRate");
@ -312,6 +313,8 @@ public class MediaPresentationDescriptionParser extends DefaultHandler
} else if (isStartTag(xpp, "SegmentTemplate")) {
segmentBase = parseSegmentTemplate(xpp, baseUrl, (SegmentTemplate) segmentBase,
periodDurationMs);
} else if (isStartTag(xpp, "ContentProtection")) {
contentProtectionsBuilder.addRepresentationProtection(parseContentProtection(xpp));
}
} while (!isEndTag(xpp, "Representation"));
@ -577,4 +580,120 @@ public class MediaPresentationDescriptionParser extends DefaultHandler
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);
}
}
}