package com.castlabs.dash.dashfragmenter.representation; import mpegDashSchemaMpd2011.*; import org.apache.commons.lang.math.Fraction; import org.w3c.dom.Attr; import org.w3c.dom.Node; import java.util.ArrayList; import java.util.Arrays; import java.util.List; /** * Pushes common attributes and elements down to the parent element. */ public class ManifestOptimizer { public static void optimize(MPDDocument mpdDocument) { for (PeriodType periodType : mpdDocument.getMPD().getPeriodArray()) { for (AdaptationSetType adaptationSetType : periodType.getAdaptationSetArray()) { optimize(adaptationSetType); adjustMinMax(adaptationSetType, "width"); adjustMinMax(adaptationSetType, "height"); adjustMinMax(adaptationSetType, "bandwidth"); adjustMinMaxFrameRate(adaptationSetType); // special handling cause the type is special (fraction) } } } public static void adjustMinMaxFrameRate(AdaptationSetType adaptationSetType) { RepresentationType representationArray[] = adaptationSetType.getRepresentationArray(); Fraction min = null, max = null; for (RepresentationType representationType : representationArray) { Node attr = representationType.getDomNode().getAttributes().getNamedItem("frameRate"); if (attr != null) { Fraction f = Fraction.getFraction(attr.getNodeValue()); min = min == null || f.compareTo(min) < 0 ? f : min; max = max == null || f.compareTo(max) > 0 ? f : max; } } if (max != null && !min.equals(max)) { // min/max doesn't make sense when both values are the same Node adaptationSet = adaptationSetType.getDomNode(); Node minAttr = adaptationSet.getOwnerDocument().createAttribute("minFrameRate"); minAttr.setNodeValue(min.toString()); adaptationSet.getAttributes().setNamedItem(minAttr); Node maxAttr = adaptationSet.getOwnerDocument().createAttribute("maxFrameRate"); maxAttr.setNodeValue(max.toString()); adaptationSet.getAttributes().setNamedItem(maxAttr); } } public static void adjustMinMax(AdaptationSetType adaptationSetType, String attrName) { RepresentationType representationArray[] = adaptationSetType.getRepresentationArray(); long min = Integer.MAX_VALUE, max = Integer.MIN_VALUE; for (RepresentationType representationType : representationArray) { Node attr = representationType.getDomNode().getAttributes().getNamedItem(attrName); if (attr != null) { int n = Integer.parseInt(attr.getNodeValue()); min = Math.min(n, min); max = Math.max(n, max); } } if (min != Integer.MAX_VALUE && min != max) { Node adaptationSet = adaptationSetType.getDomNode(); Node minAttr = adaptationSet.getOwnerDocument().createAttribute("min" + attrName.substring(0, 1).toUpperCase() + attrName.substring(1)); minAttr.setNodeValue("" + min); adaptationSet.getAttributes().setNamedItem(minAttr); Node maxAttr = adaptationSet.getOwnerDocument().createAttribute("max" + attrName.substring(0, 1).toUpperCase() + attrName.substring(1)); maxAttr.setNodeValue("" + max); adaptationSet.getAttributes().setNamedItem(maxAttr); } } public static void optimize(AdaptationSetType adaptationSetType) { optimizeContentProtection(adaptationSetType, adaptationSetType.getRepresentationArray()); optimizeAttribute(adaptationSetType, adaptationSetType.getRepresentationArray(), "mimeType"); // This is commented out as customer P0132's chromecast player doesn't look for the codecs attribute on AdaptationSet level // The deal is to update the player before 2016. If you see this here in 2016 you can remove the comment and optimize // the codecs attribute as well. // optimizeAttribute(adaptationSetType, adaptationSetType.getRepresentationArray(), "codecs"); optimizeAttribute(adaptationSetType, adaptationSetType.getRepresentationArray(), "profiles"); optimizeAttribute(adaptationSetType, adaptationSetType.getRepresentationArray(), "width"); optimizeAttribute(adaptationSetType, adaptationSetType.getRepresentationArray(), "height"); optimizeAttribute(adaptationSetType, adaptationSetType.getRepresentationArray(), "frameRate"); optimizeAttribute(adaptationSetType, adaptationSetType.getRepresentationArray(), "sar"); } private static void optimizeAttribute(AdaptationSetType adaptationSetType, RepresentationType[] representationArray, String attrName) { String value = null; for (RepresentationType representationType : representationArray) { Node attr = representationType.getDomNode().getAttributes().getNamedItem(attrName); if (attr == null) { return; // no need to move it around when it doesn't exist } String _value = attr.getNodeValue(); if (value == null || value.equals(_value)) { value = _value; } else { return; } } Node as = adaptationSetType.getDomNode(); Attr attr = as.getOwnerDocument().createAttribute(attrName); attr.setValue(value); as.getAttributes().setNamedItem(attr); for (RepresentationType representationType : representationArray) { representationType.getDomNode().getAttributes().removeNamedItem(attrName); } } public static void optimizeContentProtection(RepresentationBaseType parent, RepresentationBaseType[] children) { mpegDashSchemaMpd2011.DescriptorType[] contentProtection = new mpegDashSchemaMpd2011.DescriptorType[0]; for (RepresentationBaseType representationType : children) { if (contentProtection.length == 0) { List<DescriptorType> cpa = new ArrayList<DescriptorType>(); for (DescriptorType descriptorType : representationType.getContentProtectionArray()) { cpa.add((DescriptorType) descriptorType.copy()); } contentProtection = cpa.toArray(new DescriptorType[representationType.getContentProtectionArray().length]); } else { DescriptorType[] currentCP = representationType.getContentProtectionArray(); if (contentProtection.length == currentCP.length) { for (int i = 0; i < currentCP.length; i++) { DescriptorType a = currentCP[i]; DescriptorType b = contentProtection[i]; if (!a.xmlText().equals(b.xmlText())) { return; } } } } } if (contentProtection.length != 0) { for (RepresentationBaseType representationType : children) { representationType.setContentProtectionArray(new DescriptorType[0]); } parent.setContentProtectionArray(contentProtection); } } }