1 /* 2 * Copyright (C) 2016 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 package com.google.android.exoplayer2.source.dash.manifest; 17 18 import android.net.Uri; 19 import android.text.TextUtils; 20 import android.util.Base64; 21 import android.util.Pair; 22 import android.util.Xml; 23 import androidx.annotation.Nullable; 24 import com.google.android.exoplayer2.C; 25 import com.google.android.exoplayer2.Format; 26 import com.google.android.exoplayer2.ParserException; 27 import com.google.android.exoplayer2.drm.DrmInitData; 28 import com.google.android.exoplayer2.drm.DrmInitData.SchemeData; 29 import com.google.android.exoplayer2.extractor.mp4.PsshAtomUtil; 30 import com.google.android.exoplayer2.metadata.emsg.EventMessage; 31 import com.google.android.exoplayer2.source.dash.manifest.SegmentBase.SegmentList; 32 import com.google.android.exoplayer2.source.dash.manifest.SegmentBase.SegmentTemplate; 33 import com.google.android.exoplayer2.source.dash.manifest.SegmentBase.SegmentTimelineElement; 34 import com.google.android.exoplayer2.source.dash.manifest.SegmentBase.SingleSegmentBase; 35 import com.google.android.exoplayer2.upstream.ParsingLoadable; 36 import com.google.android.exoplayer2.util.Assertions; 37 import com.google.android.exoplayer2.util.Log; 38 import com.google.android.exoplayer2.util.MimeTypes; 39 import com.google.android.exoplayer2.util.UriUtil; 40 import com.google.android.exoplayer2.util.Util; 41 import com.google.android.exoplayer2.util.XmlPullParserUtil; 42 import java.io.ByteArrayOutputStream; 43 import java.io.IOException; 44 import java.io.InputStream; 45 import java.util.ArrayList; 46 import java.util.Collections; 47 import java.util.List; 48 import java.util.UUID; 49 import java.util.regex.Matcher; 50 import java.util.regex.Pattern; 51 import org.checkerframework.checker.nullness.compatqual.NullableType; 52 import org.xml.sax.helpers.DefaultHandler; 53 import org.xmlpull.v1.XmlPullParser; 54 import org.xmlpull.v1.XmlPullParserException; 55 import org.xmlpull.v1.XmlPullParserFactory; 56 import org.xmlpull.v1.XmlSerializer; 57 58 /** 59 * A parser of media presentation description files. 60 */ 61 public class DashManifestParser extends DefaultHandler 62 implements ParsingLoadable.Parser<DashManifest> { 63 64 private static final String TAG = "MpdParser"; 65 66 private static final Pattern FRAME_RATE_PATTERN = Pattern.compile("(\\d+)(?:/(\\d+))?"); 67 68 private static final Pattern CEA_608_ACCESSIBILITY_PATTERN = Pattern.compile("CC([1-4])=.*"); 69 private static final Pattern CEA_708_ACCESSIBILITY_PATTERN = 70 Pattern.compile("([1-9]|[1-5][0-9]|6[0-3])=.*"); 71 72 private final XmlPullParserFactory xmlParserFactory; 73 DashManifestParser()74 public DashManifestParser() { 75 try { 76 xmlParserFactory = XmlPullParserFactory.newInstance(); 77 } catch (XmlPullParserException e) { 78 throw new RuntimeException("Couldn't create XmlPullParserFactory instance", e); 79 } 80 } 81 82 // MPD parsing. 83 84 @Override parse(Uri uri, InputStream inputStream)85 public DashManifest parse(Uri uri, InputStream inputStream) throws IOException { 86 try { 87 XmlPullParser xpp = xmlParserFactory.newPullParser(); 88 xpp.setInput(inputStream, null); 89 int eventType = xpp.next(); 90 if (eventType != XmlPullParser.START_TAG || !"MPD".equals(xpp.getName())) { 91 throw new ParserException( 92 "inputStream does not contain a valid media presentation description"); 93 } 94 return parseMediaPresentationDescription(xpp, uri.toString()); 95 } catch (XmlPullParserException e) { 96 throw new ParserException(e); 97 } 98 } 99 parseMediaPresentationDescription(XmlPullParser xpp, String baseUrl)100 protected DashManifest parseMediaPresentationDescription(XmlPullParser xpp, 101 String baseUrl) throws XmlPullParserException, IOException { 102 long availabilityStartTime = parseDateTime(xpp, "availabilityStartTime", C.TIME_UNSET); 103 long durationMs = parseDuration(xpp, "mediaPresentationDuration", C.TIME_UNSET); 104 long minBufferTimeMs = parseDuration(xpp, "minBufferTime", C.TIME_UNSET); 105 String typeString = xpp.getAttributeValue(null, "type"); 106 boolean dynamic = "dynamic".equals(typeString); 107 long minUpdateTimeMs = dynamic ? parseDuration(xpp, "minimumUpdatePeriod", C.TIME_UNSET) 108 : C.TIME_UNSET; 109 long timeShiftBufferDepthMs = dynamic 110 ? parseDuration(xpp, "timeShiftBufferDepth", C.TIME_UNSET) : C.TIME_UNSET; 111 long suggestedPresentationDelayMs = dynamic 112 ? parseDuration(xpp, "suggestedPresentationDelay", C.TIME_UNSET) : C.TIME_UNSET; 113 long publishTimeMs = parseDateTime(xpp, "publishTime", C.TIME_UNSET); 114 ProgramInformation programInformation = null; 115 UtcTimingElement utcTiming = null; 116 Uri location = null; 117 118 List<Period> periods = new ArrayList<>(); 119 long nextPeriodStartMs = dynamic ? C.TIME_UNSET : 0; 120 boolean seenEarlyAccessPeriod = false; 121 boolean seenFirstBaseUrl = false; 122 do { 123 xpp.next(); 124 if (XmlPullParserUtil.isStartTag(xpp, "BaseURL")) { 125 if (!seenFirstBaseUrl) { 126 baseUrl = parseBaseUrl(xpp, baseUrl); 127 seenFirstBaseUrl = true; 128 } 129 } else if (XmlPullParserUtil.isStartTag(xpp, "ProgramInformation")) { 130 programInformation = parseProgramInformation(xpp); 131 } else if (XmlPullParserUtil.isStartTag(xpp, "UTCTiming")) { 132 utcTiming = parseUtcTiming(xpp); 133 } else if (XmlPullParserUtil.isStartTag(xpp, "Location")) { 134 location = Uri.parse(xpp.nextText()); 135 } else if (XmlPullParserUtil.isStartTag(xpp, "Period") && !seenEarlyAccessPeriod) { 136 Pair<Period, Long> periodWithDurationMs = parsePeriod(xpp, baseUrl, nextPeriodStartMs); 137 Period period = periodWithDurationMs.first; 138 if (period.startMs == C.TIME_UNSET) { 139 if (dynamic) { 140 // This is an early access period. Ignore it. All subsequent periods must also be 141 // early access. 142 seenEarlyAccessPeriod = true; 143 } else { 144 throw new ParserException("Unable to determine start of period " + periods.size()); 145 } 146 } else { 147 long periodDurationMs = periodWithDurationMs.second; 148 nextPeriodStartMs = periodDurationMs == C.TIME_UNSET ? C.TIME_UNSET 149 : (period.startMs + periodDurationMs); 150 periods.add(period); 151 } 152 } else { 153 maybeSkipTag(xpp); 154 } 155 } while (!XmlPullParserUtil.isEndTag(xpp, "MPD")); 156 157 if (durationMs == C.TIME_UNSET) { 158 if (nextPeriodStartMs != C.TIME_UNSET) { 159 // If we know the end time of the final period, we can use it as the duration. 160 durationMs = nextPeriodStartMs; 161 } else if (!dynamic) { 162 throw new ParserException("Unable to determine duration of static manifest."); 163 } 164 } 165 166 if (periods.isEmpty()) { 167 throw new ParserException("No periods found."); 168 } 169 170 return buildMediaPresentationDescription( 171 availabilityStartTime, 172 durationMs, 173 minBufferTimeMs, 174 dynamic, 175 minUpdateTimeMs, 176 timeShiftBufferDepthMs, 177 suggestedPresentationDelayMs, 178 publishTimeMs, 179 programInformation, 180 utcTiming, 181 location, 182 periods); 183 } 184 buildMediaPresentationDescription( long availabilityStartTime, long durationMs, long minBufferTimeMs, boolean dynamic, long minUpdateTimeMs, long timeShiftBufferDepthMs, long suggestedPresentationDelayMs, long publishTimeMs, @Nullable ProgramInformation programInformation, @Nullable UtcTimingElement utcTiming, @Nullable Uri location, List<Period> periods)185 protected DashManifest buildMediaPresentationDescription( 186 long availabilityStartTime, 187 long durationMs, 188 long minBufferTimeMs, 189 boolean dynamic, 190 long minUpdateTimeMs, 191 long timeShiftBufferDepthMs, 192 long suggestedPresentationDelayMs, 193 long publishTimeMs, 194 @Nullable ProgramInformation programInformation, 195 @Nullable UtcTimingElement utcTiming, 196 @Nullable Uri location, 197 List<Period> periods) { 198 return new DashManifest( 199 availabilityStartTime, 200 durationMs, 201 minBufferTimeMs, 202 dynamic, 203 minUpdateTimeMs, 204 timeShiftBufferDepthMs, 205 suggestedPresentationDelayMs, 206 publishTimeMs, 207 programInformation, 208 utcTiming, 209 location, 210 periods); 211 } 212 parseUtcTiming(XmlPullParser xpp)213 protected UtcTimingElement parseUtcTiming(XmlPullParser xpp) { 214 String schemeIdUri = xpp.getAttributeValue(null, "schemeIdUri"); 215 String value = xpp.getAttributeValue(null, "value"); 216 return buildUtcTimingElement(schemeIdUri, value); 217 } 218 buildUtcTimingElement(String schemeIdUri, String value)219 protected UtcTimingElement buildUtcTimingElement(String schemeIdUri, String value) { 220 return new UtcTimingElement(schemeIdUri, value); 221 } 222 parsePeriod(XmlPullParser xpp, String baseUrl, long defaultStartMs)223 protected Pair<Period, Long> parsePeriod(XmlPullParser xpp, String baseUrl, long defaultStartMs) 224 throws XmlPullParserException, IOException { 225 @Nullable String id = xpp.getAttributeValue(null, "id"); 226 long startMs = parseDuration(xpp, "start", defaultStartMs); 227 long durationMs = parseDuration(xpp, "duration", C.TIME_UNSET); 228 @Nullable SegmentBase segmentBase = null; 229 @Nullable Descriptor assetIdentifier = null; 230 List<AdaptationSet> adaptationSets = new ArrayList<>(); 231 List<EventStream> eventStreams = new ArrayList<>(); 232 boolean seenFirstBaseUrl = false; 233 do { 234 xpp.next(); 235 if (XmlPullParserUtil.isStartTag(xpp, "BaseURL")) { 236 if (!seenFirstBaseUrl) { 237 baseUrl = parseBaseUrl(xpp, baseUrl); 238 seenFirstBaseUrl = true; 239 } 240 } else if (XmlPullParserUtil.isStartTag(xpp, "AdaptationSet")) { 241 adaptationSets.add(parseAdaptationSet(xpp, baseUrl, segmentBase, durationMs)); 242 } else if (XmlPullParserUtil.isStartTag(xpp, "EventStream")) { 243 eventStreams.add(parseEventStream(xpp)); 244 } else if (XmlPullParserUtil.isStartTag(xpp, "SegmentBase")) { 245 segmentBase = parseSegmentBase(xpp, null); 246 } else if (XmlPullParserUtil.isStartTag(xpp, "SegmentList")) { 247 segmentBase = parseSegmentList(xpp, null, durationMs); 248 } else if (XmlPullParserUtil.isStartTag(xpp, "SegmentTemplate")) { 249 segmentBase = parseSegmentTemplate(xpp, null, Collections.emptyList(), durationMs); 250 } else if (XmlPullParserUtil.isStartTag(xpp, "AssetIdentifier")) { 251 assetIdentifier = parseDescriptor(xpp, "AssetIdentifier"); 252 } else { 253 maybeSkipTag(xpp); 254 } 255 } while (!XmlPullParserUtil.isEndTag(xpp, "Period")); 256 257 return Pair.create( 258 buildPeriod(id, startMs, adaptationSets, eventStreams, assetIdentifier), durationMs); 259 } 260 buildPeriod( @ullable String id, long startMs, List<AdaptationSet> adaptationSets, List<EventStream> eventStreams, @Nullable Descriptor assetIdentifier)261 protected Period buildPeriod( 262 @Nullable String id, 263 long startMs, 264 List<AdaptationSet> adaptationSets, 265 List<EventStream> eventStreams, 266 @Nullable Descriptor assetIdentifier) { 267 return new Period(id, startMs, adaptationSets, eventStreams, assetIdentifier); 268 } 269 270 // AdaptationSet parsing. 271 parseAdaptationSet( XmlPullParser xpp, String baseUrl, @Nullable SegmentBase segmentBase, long periodDurationMs)272 protected AdaptationSet parseAdaptationSet( 273 XmlPullParser xpp, String baseUrl, @Nullable SegmentBase segmentBase, long periodDurationMs) 274 throws XmlPullParserException, IOException { 275 int id = parseInt(xpp, "id", AdaptationSet.ID_UNSET); 276 int contentType = parseContentType(xpp); 277 278 String mimeType = xpp.getAttributeValue(null, "mimeType"); 279 String codecs = xpp.getAttributeValue(null, "codecs"); 280 int width = parseInt(xpp, "width", Format.NO_VALUE); 281 int height = parseInt(xpp, "height", Format.NO_VALUE); 282 float frameRate = parseFrameRate(xpp, Format.NO_VALUE); 283 int audioChannels = Format.NO_VALUE; 284 int audioSamplingRate = parseInt(xpp, "audioSamplingRate", Format.NO_VALUE); 285 String language = xpp.getAttributeValue(null, "lang"); 286 String label = xpp.getAttributeValue(null, "label"); 287 String drmSchemeType = null; 288 ArrayList<SchemeData> drmSchemeDatas = new ArrayList<>(); 289 ArrayList<Descriptor> inbandEventStreams = new ArrayList<>(); 290 ArrayList<Descriptor> accessibilityDescriptors = new ArrayList<>(); 291 ArrayList<Descriptor> roleDescriptors = new ArrayList<>(); 292 ArrayList<Descriptor> essentialProperties = new ArrayList<>(); 293 ArrayList<Descriptor> supplementalProperties = new ArrayList<>(); 294 List<RepresentationInfo> representationInfos = new ArrayList<>(); 295 296 boolean seenFirstBaseUrl = false; 297 do { 298 xpp.next(); 299 if (XmlPullParserUtil.isStartTag(xpp, "BaseURL")) { 300 if (!seenFirstBaseUrl) { 301 baseUrl = parseBaseUrl(xpp, baseUrl); 302 seenFirstBaseUrl = true; 303 } 304 } else if (XmlPullParserUtil.isStartTag(xpp, "ContentProtection")) { 305 Pair<String, SchemeData> contentProtection = parseContentProtection(xpp); 306 if (contentProtection.first != null) { 307 drmSchemeType = contentProtection.first; 308 } 309 if (contentProtection.second != null) { 310 drmSchemeDatas.add(contentProtection.second); 311 } 312 } else if (XmlPullParserUtil.isStartTag(xpp, "ContentComponent")) { 313 language = checkLanguageConsistency(language, xpp.getAttributeValue(null, "lang")); 314 contentType = checkContentTypeConsistency(contentType, parseContentType(xpp)); 315 } else if (XmlPullParserUtil.isStartTag(xpp, "Role")) { 316 roleDescriptors.add(parseDescriptor(xpp, "Role")); 317 } else if (XmlPullParserUtil.isStartTag(xpp, "AudioChannelConfiguration")) { 318 audioChannels = parseAudioChannelConfiguration(xpp); 319 } else if (XmlPullParserUtil.isStartTag(xpp, "Accessibility")) { 320 accessibilityDescriptors.add(parseDescriptor(xpp, "Accessibility")); 321 } else if (XmlPullParserUtil.isStartTag(xpp, "EssentialProperty")) { 322 essentialProperties.add(parseDescriptor(xpp, "EssentialProperty")); 323 } else if (XmlPullParserUtil.isStartTag(xpp, "SupplementalProperty")) { 324 supplementalProperties.add(parseDescriptor(xpp, "SupplementalProperty")); 325 } else if (XmlPullParserUtil.isStartTag(xpp, "Representation")) { 326 RepresentationInfo representationInfo = 327 parseRepresentation( 328 xpp, 329 baseUrl, 330 mimeType, 331 codecs, 332 width, 333 height, 334 frameRate, 335 audioChannels, 336 audioSamplingRate, 337 language, 338 roleDescriptors, 339 accessibilityDescriptors, 340 essentialProperties, 341 supplementalProperties, 342 segmentBase, 343 periodDurationMs); 344 contentType = 345 checkContentTypeConsistency( 346 contentType, MimeTypes.getTrackType(representationInfo.format.sampleMimeType)); 347 representationInfos.add(representationInfo); 348 } else if (XmlPullParserUtil.isStartTag(xpp, "SegmentBase")) { 349 segmentBase = parseSegmentBase(xpp, (SingleSegmentBase) segmentBase); 350 } else if (XmlPullParserUtil.isStartTag(xpp, "SegmentList")) { 351 segmentBase = parseSegmentList(xpp, (SegmentList) segmentBase, periodDurationMs); 352 } else if (XmlPullParserUtil.isStartTag(xpp, "SegmentTemplate")) { 353 segmentBase = 354 parseSegmentTemplate( 355 xpp, (SegmentTemplate) segmentBase, supplementalProperties, periodDurationMs); 356 } else if (XmlPullParserUtil.isStartTag(xpp, "InbandEventStream")) { 357 inbandEventStreams.add(parseDescriptor(xpp, "InbandEventStream")); 358 } else if (XmlPullParserUtil.isStartTag(xpp, "Label")) { 359 label = parseLabel(xpp); 360 } else if (XmlPullParserUtil.isStartTag(xpp)) { 361 parseAdaptationSetChild(xpp); 362 } 363 } while (!XmlPullParserUtil.isEndTag(xpp, "AdaptationSet")); 364 365 // Build the representations. 366 List<Representation> representations = new ArrayList<>(representationInfos.size()); 367 for (int i = 0; i < representationInfos.size(); i++) { 368 representations.add( 369 buildRepresentation( 370 representationInfos.get(i), 371 label, 372 drmSchemeType, 373 drmSchemeDatas, 374 inbandEventStreams)); 375 } 376 377 return buildAdaptationSet( 378 id, 379 contentType, 380 representations, 381 accessibilityDescriptors, 382 essentialProperties, 383 supplementalProperties); 384 } 385 buildAdaptationSet( int id, int contentType, List<Representation> representations, List<Descriptor> accessibilityDescriptors, List<Descriptor> essentialProperties, List<Descriptor> supplementalProperties)386 protected AdaptationSet buildAdaptationSet( 387 int id, 388 int contentType, 389 List<Representation> representations, 390 List<Descriptor> accessibilityDescriptors, 391 List<Descriptor> essentialProperties, 392 List<Descriptor> supplementalProperties) { 393 return new AdaptationSet( 394 id, 395 contentType, 396 representations, 397 accessibilityDescriptors, 398 essentialProperties, 399 supplementalProperties); 400 } 401 parseContentType(XmlPullParser xpp)402 protected int parseContentType(XmlPullParser xpp) { 403 String contentType = xpp.getAttributeValue(null, "contentType"); 404 return TextUtils.isEmpty(contentType) ? C.TRACK_TYPE_UNKNOWN 405 : MimeTypes.BASE_TYPE_AUDIO.equals(contentType) ? C.TRACK_TYPE_AUDIO 406 : MimeTypes.BASE_TYPE_VIDEO.equals(contentType) ? C.TRACK_TYPE_VIDEO 407 : MimeTypes.BASE_TYPE_TEXT.equals(contentType) ? C.TRACK_TYPE_TEXT 408 : C.TRACK_TYPE_UNKNOWN; 409 } 410 411 /** 412 * Parses a ContentProtection element. 413 * 414 * @param xpp The parser from which to read. 415 * @throws XmlPullParserException If an error occurs parsing the element. 416 * @throws IOException If an error occurs reading the element. 417 * @return The scheme type and/or {@link SchemeData} parsed from the ContentProtection element. 418 * Either or both may be null, depending on the ContentProtection element being parsed. 419 */ parseContentProtection( XmlPullParser xpp)420 protected Pair<@NullableType String, @NullableType SchemeData> parseContentProtection( 421 XmlPullParser xpp) throws XmlPullParserException, IOException { 422 String schemeType = null; 423 String licenseServerUrl = null; 424 byte[] data = null; 425 UUID uuid = null; 426 427 String schemeIdUri = xpp.getAttributeValue(null, "schemeIdUri"); 428 if (schemeIdUri != null) { 429 switch (Util.toLowerInvariant(schemeIdUri)) { 430 case "urn:mpeg:dash:mp4protection:2011": 431 schemeType = xpp.getAttributeValue(null, "value"); 432 String defaultKid = XmlPullParserUtil.getAttributeValueIgnorePrefix(xpp, "default_KID"); 433 if (!TextUtils.isEmpty(defaultKid) 434 && !"00000000-0000-0000-0000-000000000000".equals(defaultKid)) { 435 String[] defaultKidStrings = defaultKid.split("\\s+"); 436 UUID[] defaultKids = new UUID[defaultKidStrings.length]; 437 for (int i = 0; i < defaultKidStrings.length; i++) { 438 defaultKids[i] = UUID.fromString(defaultKidStrings[i]); 439 } 440 data = PsshAtomUtil.buildPsshAtom(C.COMMON_PSSH_UUID, defaultKids, null); 441 uuid = C.COMMON_PSSH_UUID; 442 } 443 break; 444 case "urn:uuid:9a04f079-9840-4286-ab92-e65be0885f95": 445 uuid = C.PLAYREADY_UUID; 446 break; 447 case "urn:uuid:edef8ba9-79d6-4ace-a3c8-27dcd51d21ed": 448 uuid = C.WIDEVINE_UUID; 449 break; 450 default: 451 break; 452 } 453 } 454 455 do { 456 xpp.next(); 457 if (XmlPullParserUtil.isStartTag(xpp, "ms:laurl")) { 458 licenseServerUrl = xpp.getAttributeValue(null, "licenseUrl"); 459 } else if (data == null 460 && XmlPullParserUtil.isStartTagIgnorePrefix(xpp, "pssh") 461 && xpp.next() == XmlPullParser.TEXT) { 462 // The cenc:pssh element is defined in 23001-7:2015. 463 data = Base64.decode(xpp.getText(), Base64.DEFAULT); 464 uuid = PsshAtomUtil.parseUuid(data); 465 if (uuid == null) { 466 Log.w(TAG, "Skipping malformed cenc:pssh data"); 467 data = null; 468 } 469 } else if (data == null 470 && C.PLAYREADY_UUID.equals(uuid) 471 && XmlPullParserUtil.isStartTag(xpp, "mspr:pro") 472 && xpp.next() == XmlPullParser.TEXT) { 473 // The mspr:pro element is defined in DASH Content Protection using Microsoft PlayReady. 474 data = 475 PsshAtomUtil.buildPsshAtom( 476 C.PLAYREADY_UUID, Base64.decode(xpp.getText(), Base64.DEFAULT)); 477 } else { 478 maybeSkipTag(xpp); 479 } 480 } while (!XmlPullParserUtil.isEndTag(xpp, "ContentProtection")); 481 SchemeData schemeData = 482 uuid != null ? new SchemeData(uuid, licenseServerUrl, MimeTypes.VIDEO_MP4, data) : null; 483 return Pair.create(schemeType, schemeData); 484 } 485 486 /** 487 * Parses children of AdaptationSet elements not specifically parsed elsewhere. 488 * 489 * @param xpp The XmpPullParser from which the AdaptationSet child should be parsed. 490 * @throws XmlPullParserException If an error occurs parsing the element. 491 * @throws IOException If an error occurs reading the element. 492 */ parseAdaptationSetChild(XmlPullParser xpp)493 protected void parseAdaptationSetChild(XmlPullParser xpp) 494 throws XmlPullParserException, IOException { 495 maybeSkipTag(xpp); 496 } 497 498 // Representation parsing. 499 parseRepresentation( XmlPullParser xpp, String baseUrl, @Nullable String adaptationSetMimeType, @Nullable String adaptationSetCodecs, int adaptationSetWidth, int adaptationSetHeight, float adaptationSetFrameRate, int adaptationSetAudioChannels, int adaptationSetAudioSamplingRate, @Nullable String adaptationSetLanguage, List<Descriptor> adaptationSetRoleDescriptors, List<Descriptor> adaptationSetAccessibilityDescriptors, List<Descriptor> adaptationSetEssentialProperties, List<Descriptor> adaptationSetSupplementalProperties, @Nullable SegmentBase segmentBase, long periodDurationMs)500 protected RepresentationInfo parseRepresentation( 501 XmlPullParser xpp, 502 String baseUrl, 503 @Nullable String adaptationSetMimeType, 504 @Nullable String adaptationSetCodecs, 505 int adaptationSetWidth, 506 int adaptationSetHeight, 507 float adaptationSetFrameRate, 508 int adaptationSetAudioChannels, 509 int adaptationSetAudioSamplingRate, 510 @Nullable String adaptationSetLanguage, 511 List<Descriptor> adaptationSetRoleDescriptors, 512 List<Descriptor> adaptationSetAccessibilityDescriptors, 513 List<Descriptor> adaptationSetEssentialProperties, 514 List<Descriptor> adaptationSetSupplementalProperties, 515 @Nullable SegmentBase segmentBase, 516 long periodDurationMs) 517 throws XmlPullParserException, IOException { 518 String id = xpp.getAttributeValue(null, "id"); 519 int bandwidth = parseInt(xpp, "bandwidth", Format.NO_VALUE); 520 521 String mimeType = parseString(xpp, "mimeType", adaptationSetMimeType); 522 String codecs = parseString(xpp, "codecs", adaptationSetCodecs); 523 int width = parseInt(xpp, "width", adaptationSetWidth); 524 int height = parseInt(xpp, "height", adaptationSetHeight); 525 float frameRate = parseFrameRate(xpp, adaptationSetFrameRate); 526 int audioChannels = adaptationSetAudioChannels; 527 int audioSamplingRate = parseInt(xpp, "audioSamplingRate", adaptationSetAudioSamplingRate); 528 String drmSchemeType = null; 529 ArrayList<SchemeData> drmSchemeDatas = new ArrayList<>(); 530 ArrayList<Descriptor> inbandEventStreams = new ArrayList<>(); 531 ArrayList<Descriptor> essentialProperties = new ArrayList<>(adaptationSetEssentialProperties); 532 ArrayList<Descriptor> supplementalProperties = 533 new ArrayList<>(adaptationSetSupplementalProperties); 534 535 boolean seenFirstBaseUrl = false; 536 do { 537 xpp.next(); 538 if (XmlPullParserUtil.isStartTag(xpp, "BaseURL")) { 539 if (!seenFirstBaseUrl) { 540 baseUrl = parseBaseUrl(xpp, baseUrl); 541 seenFirstBaseUrl = true; 542 } 543 } else if (XmlPullParserUtil.isStartTag(xpp, "AudioChannelConfiguration")) { 544 audioChannels = parseAudioChannelConfiguration(xpp); 545 } else if (XmlPullParserUtil.isStartTag(xpp, "SegmentBase")) { 546 segmentBase = parseSegmentBase(xpp, (SingleSegmentBase) segmentBase); 547 } else if (XmlPullParserUtil.isStartTag(xpp, "SegmentList")) { 548 segmentBase = parseSegmentList(xpp, (SegmentList) segmentBase, periodDurationMs); 549 } else if (XmlPullParserUtil.isStartTag(xpp, "SegmentTemplate")) { 550 segmentBase = 551 parseSegmentTemplate( 552 xpp, 553 (SegmentTemplate) segmentBase, 554 adaptationSetSupplementalProperties, 555 periodDurationMs); 556 } else if (XmlPullParserUtil.isStartTag(xpp, "ContentProtection")) { 557 Pair<String, SchemeData> contentProtection = parseContentProtection(xpp); 558 if (contentProtection.first != null) { 559 drmSchemeType = contentProtection.first; 560 } 561 if (contentProtection.second != null) { 562 drmSchemeDatas.add(contentProtection.second); 563 } 564 } else if (XmlPullParserUtil.isStartTag(xpp, "InbandEventStream")) { 565 inbandEventStreams.add(parseDescriptor(xpp, "InbandEventStream")); 566 } else if (XmlPullParserUtil.isStartTag(xpp, "EssentialProperty")) { 567 essentialProperties.add(parseDescriptor(xpp, "EssentialProperty")); 568 } else if (XmlPullParserUtil.isStartTag(xpp, "SupplementalProperty")) { 569 supplementalProperties.add(parseDescriptor(xpp, "SupplementalProperty")); 570 } else { 571 maybeSkipTag(xpp); 572 } 573 } while (!XmlPullParserUtil.isEndTag(xpp, "Representation")); 574 575 Format format = 576 buildFormat( 577 id, 578 mimeType, 579 width, 580 height, 581 frameRate, 582 audioChannels, 583 audioSamplingRate, 584 bandwidth, 585 adaptationSetLanguage, 586 adaptationSetRoleDescriptors, 587 adaptationSetAccessibilityDescriptors, 588 codecs, 589 essentialProperties, 590 supplementalProperties); 591 segmentBase = segmentBase != null ? segmentBase : new SingleSegmentBase(); 592 593 return new RepresentationInfo(format, baseUrl, segmentBase, drmSchemeType, drmSchemeDatas, 594 inbandEventStreams, Representation.REVISION_ID_DEFAULT); 595 } 596 buildFormat( @ullable String id, @Nullable String containerMimeType, int width, int height, float frameRate, int audioChannels, int audioSamplingRate, int bitrate, @Nullable String language, List<Descriptor> roleDescriptors, List<Descriptor> accessibilityDescriptors, @Nullable String codecs, List<Descriptor> essentialProperties, List<Descriptor> supplementalProperties)597 protected Format buildFormat( 598 @Nullable String id, 599 @Nullable String containerMimeType, 600 int width, 601 int height, 602 float frameRate, 603 int audioChannels, 604 int audioSamplingRate, 605 int bitrate, 606 @Nullable String language, 607 List<Descriptor> roleDescriptors, 608 List<Descriptor> accessibilityDescriptors, 609 @Nullable String codecs, 610 List<Descriptor> essentialProperties, 611 List<Descriptor> supplementalProperties) { 612 @Nullable String sampleMimeType = getSampleMimeType(containerMimeType, codecs); 613 if (MimeTypes.AUDIO_E_AC3.equals(sampleMimeType)) { 614 sampleMimeType = parseEac3SupplementalProperties(supplementalProperties); 615 } 616 @C.SelectionFlags int selectionFlags = parseSelectionFlagsFromRoleDescriptors(roleDescriptors); 617 @C.RoleFlags int roleFlags = parseRoleFlagsFromRoleDescriptors(roleDescriptors); 618 roleFlags |= parseRoleFlagsFromAccessibilityDescriptors(accessibilityDescriptors); 619 roleFlags |= parseRoleFlagsFromProperties(essentialProperties); 620 roleFlags |= parseRoleFlagsFromProperties(supplementalProperties); 621 622 Format.Builder formatBuilder = 623 new Format.Builder() 624 .setId(id) 625 .setContainerMimeType(containerMimeType) 626 .setSampleMimeType(sampleMimeType) 627 .setCodecs(codecs) 628 .setPeakBitrate(bitrate) 629 .setSelectionFlags(selectionFlags) 630 .setRoleFlags(roleFlags) 631 .setLanguage(language); 632 633 if (MimeTypes.isVideo(sampleMimeType)) { 634 formatBuilder.setWidth(width).setHeight(height).setFrameRate(frameRate); 635 } else if (MimeTypes.isAudio(sampleMimeType)) { 636 formatBuilder.setChannelCount(audioChannels).setSampleRate(audioSamplingRate); 637 } else if (MimeTypes.isText(sampleMimeType)) { 638 int accessibilityChannel = Format.NO_VALUE; 639 if (MimeTypes.APPLICATION_CEA608.equals(sampleMimeType)) { 640 accessibilityChannel = parseCea608AccessibilityChannel(accessibilityDescriptors); 641 } else if (MimeTypes.APPLICATION_CEA708.equals(sampleMimeType)) { 642 accessibilityChannel = parseCea708AccessibilityChannel(accessibilityDescriptors); 643 } 644 formatBuilder.setAccessibilityChannel(accessibilityChannel); 645 } 646 647 return formatBuilder.build(); 648 } 649 buildRepresentation( RepresentationInfo representationInfo, @Nullable String label, @Nullable String extraDrmSchemeType, ArrayList<SchemeData> extraDrmSchemeDatas, ArrayList<Descriptor> extraInbandEventStreams)650 protected Representation buildRepresentation( 651 RepresentationInfo representationInfo, 652 @Nullable String label, 653 @Nullable String extraDrmSchemeType, 654 ArrayList<SchemeData> extraDrmSchemeDatas, 655 ArrayList<Descriptor> extraInbandEventStreams) { 656 Format.Builder formatBuilder = representationInfo.format.buildUpon(); 657 if (label != null) { 658 formatBuilder.setLabel(label); 659 } 660 @Nullable String drmSchemeType = representationInfo.drmSchemeType; 661 if (drmSchemeType == null) { 662 drmSchemeType = extraDrmSchemeType; 663 } 664 ArrayList<SchemeData> drmSchemeDatas = representationInfo.drmSchemeDatas; 665 drmSchemeDatas.addAll(extraDrmSchemeDatas); 666 if (!drmSchemeDatas.isEmpty()) { 667 filterRedundantIncompleteSchemeDatas(drmSchemeDatas); 668 formatBuilder.setDrmInitData(new DrmInitData(drmSchemeType, drmSchemeDatas)); 669 } 670 ArrayList<Descriptor> inbandEventStreams = representationInfo.inbandEventStreams; 671 inbandEventStreams.addAll(extraInbandEventStreams); 672 return Representation.newInstance( 673 representationInfo.revisionId, 674 formatBuilder.build(), 675 representationInfo.baseUrl, 676 representationInfo.segmentBase, 677 inbandEventStreams); 678 } 679 680 // SegmentBase, SegmentList and SegmentTemplate parsing. 681 parseSegmentBase( XmlPullParser xpp, @Nullable SingleSegmentBase parent)682 protected SingleSegmentBase parseSegmentBase( 683 XmlPullParser xpp, @Nullable SingleSegmentBase parent) 684 throws XmlPullParserException, IOException { 685 686 long timescale = parseLong(xpp, "timescale", parent != null ? parent.timescale : 1); 687 long presentationTimeOffset = parseLong(xpp, "presentationTimeOffset", 688 parent != null ? parent.presentationTimeOffset : 0); 689 690 long indexStart = parent != null ? parent.indexStart : 0; 691 long indexLength = parent != null ? parent.indexLength : 0; 692 String indexRangeText = xpp.getAttributeValue(null, "indexRange"); 693 if (indexRangeText != null) { 694 String[] indexRange = indexRangeText.split("-"); 695 indexStart = Long.parseLong(indexRange[0]); 696 indexLength = Long.parseLong(indexRange[1]) - indexStart + 1; 697 } 698 699 @Nullable RangedUri initialization = parent != null ? parent.initialization : null; 700 do { 701 xpp.next(); 702 if (XmlPullParserUtil.isStartTag(xpp, "Initialization")) { 703 initialization = parseInitialization(xpp); 704 } else { 705 maybeSkipTag(xpp); 706 } 707 } while (!XmlPullParserUtil.isEndTag(xpp, "SegmentBase")); 708 709 return buildSingleSegmentBase(initialization, timescale, presentationTimeOffset, indexStart, 710 indexLength); 711 } 712 buildSingleSegmentBase(RangedUri initialization, long timescale, long presentationTimeOffset, long indexStart, long indexLength)713 protected SingleSegmentBase buildSingleSegmentBase(RangedUri initialization, long timescale, 714 long presentationTimeOffset, long indexStart, long indexLength) { 715 return new SingleSegmentBase(initialization, timescale, presentationTimeOffset, indexStart, 716 indexLength); 717 } 718 parseSegmentList( XmlPullParser xpp, @Nullable SegmentList parent, long periodDurationMs)719 protected SegmentList parseSegmentList( 720 XmlPullParser xpp, @Nullable SegmentList parent, long periodDurationMs) 721 throws XmlPullParserException, IOException { 722 723 long timescale = parseLong(xpp, "timescale", parent != null ? parent.timescale : 1); 724 long presentationTimeOffset = parseLong(xpp, "presentationTimeOffset", 725 parent != null ? parent.presentationTimeOffset : 0); 726 long duration = parseLong(xpp, "duration", parent != null ? parent.duration : C.TIME_UNSET); 727 long startNumber = parseLong(xpp, "startNumber", parent != null ? parent.startNumber : 1); 728 729 RangedUri initialization = null; 730 List<SegmentTimelineElement> timeline = null; 731 List<RangedUri> segments = null; 732 733 do { 734 xpp.next(); 735 if (XmlPullParserUtil.isStartTag(xpp, "Initialization")) { 736 initialization = parseInitialization(xpp); 737 } else if (XmlPullParserUtil.isStartTag(xpp, "SegmentTimeline")) { 738 timeline = parseSegmentTimeline(xpp, timescale, periodDurationMs); 739 } else if (XmlPullParserUtil.isStartTag(xpp, "SegmentURL")) { 740 if (segments == null) { 741 segments = new ArrayList<>(); 742 } 743 segments.add(parseSegmentUrl(xpp)); 744 } else { 745 maybeSkipTag(xpp); 746 } 747 } while (!XmlPullParserUtil.isEndTag(xpp, "SegmentList")); 748 749 if (parent != null) { 750 initialization = initialization != null ? initialization : parent.initialization; 751 timeline = timeline != null ? timeline : parent.segmentTimeline; 752 segments = segments != null ? segments : parent.mediaSegments; 753 } 754 755 return buildSegmentList(initialization, timescale, presentationTimeOffset, 756 startNumber, duration, timeline, segments); 757 } 758 buildSegmentList( RangedUri initialization, long timescale, long presentationTimeOffset, long startNumber, long duration, @Nullable List<SegmentTimelineElement> timeline, @Nullable List<RangedUri> segments)759 protected SegmentList buildSegmentList( 760 RangedUri initialization, 761 long timescale, 762 long presentationTimeOffset, 763 long startNumber, 764 long duration, 765 @Nullable List<SegmentTimelineElement> timeline, 766 @Nullable List<RangedUri> segments) { 767 return new SegmentList(initialization, timescale, presentationTimeOffset, 768 startNumber, duration, timeline, segments); 769 } 770 parseSegmentTemplate( XmlPullParser xpp, @Nullable SegmentTemplate parent, List<Descriptor> adaptationSetSupplementalProperties, long periodDurationMs)771 protected SegmentTemplate parseSegmentTemplate( 772 XmlPullParser xpp, 773 @Nullable SegmentTemplate parent, 774 List<Descriptor> adaptationSetSupplementalProperties, 775 long periodDurationMs) 776 throws XmlPullParserException, IOException { 777 long timescale = parseLong(xpp, "timescale", parent != null ? parent.timescale : 1); 778 long presentationTimeOffset = parseLong(xpp, "presentationTimeOffset", 779 parent != null ? parent.presentationTimeOffset : 0); 780 long duration = parseLong(xpp, "duration", parent != null ? parent.duration : C.TIME_UNSET); 781 long startNumber = parseLong(xpp, "startNumber", parent != null ? parent.startNumber : 1); 782 long endNumber = 783 parseLastSegmentNumberSupplementalProperty(adaptationSetSupplementalProperties); 784 785 UrlTemplate mediaTemplate = parseUrlTemplate(xpp, "media", 786 parent != null ? parent.mediaTemplate : null); 787 UrlTemplate initializationTemplate = parseUrlTemplate(xpp, "initialization", 788 parent != null ? parent.initializationTemplate : null); 789 790 RangedUri initialization = null; 791 List<SegmentTimelineElement> timeline = null; 792 793 do { 794 xpp.next(); 795 if (XmlPullParserUtil.isStartTag(xpp, "Initialization")) { 796 initialization = parseInitialization(xpp); 797 } else if (XmlPullParserUtil.isStartTag(xpp, "SegmentTimeline")) { 798 timeline = parseSegmentTimeline(xpp, timescale, periodDurationMs); 799 } else { 800 maybeSkipTag(xpp); 801 } 802 } while (!XmlPullParserUtil.isEndTag(xpp, "SegmentTemplate")); 803 804 if (parent != null) { 805 initialization = initialization != null ? initialization : parent.initialization; 806 timeline = timeline != null ? timeline : parent.segmentTimeline; 807 } 808 809 return buildSegmentTemplate( 810 initialization, 811 timescale, 812 presentationTimeOffset, 813 startNumber, 814 endNumber, 815 duration, 816 timeline, 817 initializationTemplate, 818 mediaTemplate); 819 } 820 buildSegmentTemplate( RangedUri initialization, long timescale, long presentationTimeOffset, long startNumber, long endNumber, long duration, List<SegmentTimelineElement> timeline, @Nullable UrlTemplate initializationTemplate, @Nullable UrlTemplate mediaTemplate)821 protected SegmentTemplate buildSegmentTemplate( 822 RangedUri initialization, 823 long timescale, 824 long presentationTimeOffset, 825 long startNumber, 826 long endNumber, 827 long duration, 828 List<SegmentTimelineElement> timeline, 829 @Nullable UrlTemplate initializationTemplate, 830 @Nullable UrlTemplate mediaTemplate) { 831 return new SegmentTemplate( 832 initialization, 833 timescale, 834 presentationTimeOffset, 835 startNumber, 836 endNumber, 837 duration, 838 timeline, 839 initializationTemplate, 840 mediaTemplate); 841 } 842 843 /** 844 * /** 845 * Parses a single EventStream node in the manifest. 846 * <p> 847 * @param xpp The current xml parser. 848 * @return The {@link EventStream} parsed from this EventStream node. 849 * @throws XmlPullParserException If there is any error parsing this node. 850 * @throws IOException If there is any error reading from the underlying input stream. 851 */ parseEventStream(XmlPullParser xpp)852 protected EventStream parseEventStream(XmlPullParser xpp) 853 throws XmlPullParserException, IOException { 854 String schemeIdUri = parseString(xpp, "schemeIdUri", ""); 855 String value = parseString(xpp, "value", ""); 856 long timescale = parseLong(xpp, "timescale", 1); 857 List<Pair<Long, EventMessage>> eventMessages = new ArrayList<>(); 858 ByteArrayOutputStream scratchOutputStream = new ByteArrayOutputStream(512); 859 do { 860 xpp.next(); 861 if (XmlPullParserUtil.isStartTag(xpp, "Event")) { 862 Pair<Long, EventMessage> event = 863 parseEvent(xpp, schemeIdUri, value, timescale, scratchOutputStream); 864 eventMessages.add(event); 865 } else { 866 maybeSkipTag(xpp); 867 } 868 } while (!XmlPullParserUtil.isEndTag(xpp, "EventStream")); 869 870 long[] presentationTimesUs = new long[eventMessages.size()]; 871 EventMessage[] events = new EventMessage[eventMessages.size()]; 872 for (int i = 0; i < eventMessages.size(); i++) { 873 Pair<Long, EventMessage> event = eventMessages.get(i); 874 presentationTimesUs[i] = event.first; 875 events[i] = event.second; 876 } 877 return buildEventStream(schemeIdUri, value, timescale, presentationTimesUs, events); 878 } 879 buildEventStream(String schemeIdUri, String value, long timescale, long[] presentationTimesUs, EventMessage[] events)880 protected EventStream buildEventStream(String schemeIdUri, String value, long timescale, 881 long[] presentationTimesUs, EventMessage[] events) { 882 return new EventStream(schemeIdUri, value, timescale, presentationTimesUs, events); 883 } 884 885 /** 886 * Parses a single Event node in the manifest. 887 * 888 * @param xpp The current xml parser. 889 * @param schemeIdUri The schemeIdUri of the parent EventStream. 890 * @param value The schemeIdUri of the parent EventStream. 891 * @param timescale The timescale of the parent EventStream. 892 * @param scratchOutputStream A {@link ByteArrayOutputStream} that is used when parsing event 893 * objects. 894 * @return A pair containing the node's presentation timestamp in microseconds and the parsed 895 * {@link EventMessage}. 896 * @throws XmlPullParserException If there is any error parsing this node. 897 * @throws IOException If there is any error reading from the underlying input stream. 898 */ parseEvent( XmlPullParser xpp, String schemeIdUri, String value, long timescale, ByteArrayOutputStream scratchOutputStream)899 protected Pair<Long, EventMessage> parseEvent( 900 XmlPullParser xpp, 901 String schemeIdUri, 902 String value, 903 long timescale, 904 ByteArrayOutputStream scratchOutputStream) 905 throws IOException, XmlPullParserException { 906 long id = parseLong(xpp, "id", 0); 907 long duration = parseLong(xpp, "duration", C.TIME_UNSET); 908 long presentationTime = parseLong(xpp, "presentationTime", 0); 909 long durationMs = Util.scaleLargeTimestamp(duration, C.MILLIS_PER_SECOND, timescale); 910 long presentationTimesUs = Util.scaleLargeTimestamp(presentationTime, C.MICROS_PER_SECOND, 911 timescale); 912 String messageData = parseString(xpp, "messageData", null); 913 byte[] eventObject = parseEventObject(xpp, scratchOutputStream); 914 return Pair.create( 915 presentationTimesUs, 916 buildEvent( 917 schemeIdUri, 918 value, 919 id, 920 durationMs, 921 messageData == null ? eventObject : Util.getUtf8Bytes(messageData))); 922 } 923 924 /** 925 * Parses an event object. 926 * 927 * @param xpp The current xml parser. 928 * @param scratchOutputStream A {@link ByteArrayOutputStream} that's used when parsing the object. 929 * @return The serialized byte array. 930 * @throws XmlPullParserException If there is any error parsing this node. 931 * @throws IOException If there is any error reading from the underlying input stream. 932 */ parseEventObject(XmlPullParser xpp, ByteArrayOutputStream scratchOutputStream)933 protected byte[] parseEventObject(XmlPullParser xpp, ByteArrayOutputStream scratchOutputStream) 934 throws XmlPullParserException, IOException { 935 scratchOutputStream.reset(); 936 XmlSerializer xmlSerializer = Xml.newSerializer(); 937 xmlSerializer.setOutput(scratchOutputStream, C.UTF8_NAME); 938 // Start reading everything between <Event> and </Event>, and serialize them into an Xml 939 // byte array. 940 xpp.nextToken(); 941 while (!XmlPullParserUtil.isEndTag(xpp, "Event")) { 942 switch (xpp.getEventType()) { 943 case (XmlPullParser.START_DOCUMENT): 944 xmlSerializer.startDocument(null, false); 945 break; 946 case (XmlPullParser.END_DOCUMENT): 947 xmlSerializer.endDocument(); 948 break; 949 case (XmlPullParser.START_TAG): 950 xmlSerializer.startTag(xpp.getNamespace(), xpp.getName()); 951 for (int i = 0; i < xpp.getAttributeCount(); i++) { 952 xmlSerializer.attribute(xpp.getAttributeNamespace(i), xpp.getAttributeName(i), 953 xpp.getAttributeValue(i)); 954 } 955 break; 956 case (XmlPullParser.END_TAG): 957 xmlSerializer.endTag(xpp.getNamespace(), xpp.getName()); 958 break; 959 case (XmlPullParser.TEXT): 960 xmlSerializer.text(xpp.getText()); 961 break; 962 case (XmlPullParser.CDSECT): 963 xmlSerializer.cdsect(xpp.getText()); 964 break; 965 case (XmlPullParser.ENTITY_REF): 966 xmlSerializer.entityRef(xpp.getText()); 967 break; 968 case (XmlPullParser.IGNORABLE_WHITESPACE): 969 xmlSerializer.ignorableWhitespace(xpp.getText()); 970 break; 971 case (XmlPullParser.PROCESSING_INSTRUCTION): 972 xmlSerializer.processingInstruction(xpp.getText()); 973 break; 974 case (XmlPullParser.COMMENT): 975 xmlSerializer.comment(xpp.getText()); 976 break; 977 case (XmlPullParser.DOCDECL): 978 xmlSerializer.docdecl(xpp.getText()); 979 break; 980 default: // fall out 981 } 982 xpp.nextToken(); 983 } 984 xmlSerializer.flush(); 985 return scratchOutputStream.toByteArray(); 986 } 987 buildEvent( String schemeIdUri, String value, long id, long durationMs, byte[] messageData)988 protected EventMessage buildEvent( 989 String schemeIdUri, String value, long id, long durationMs, byte[] messageData) { 990 return new EventMessage(schemeIdUri, value, durationMs, id, messageData); 991 } 992 parseSegmentTimeline( XmlPullParser xpp, long timescale, long periodDurationMs)993 protected List<SegmentTimelineElement> parseSegmentTimeline( 994 XmlPullParser xpp, long timescale, long periodDurationMs) 995 throws XmlPullParserException, IOException { 996 List<SegmentTimelineElement> segmentTimeline = new ArrayList<>(); 997 long startTime = 0; 998 long elementDuration = C.TIME_UNSET; 999 int elementRepeatCount = 0; 1000 boolean havePreviousTimelineElement = false; 1001 do { 1002 xpp.next(); 1003 if (XmlPullParserUtil.isStartTag(xpp, "S")) { 1004 long newStartTime = parseLong(xpp, "t", C.TIME_UNSET); 1005 if (havePreviousTimelineElement) { 1006 startTime = 1007 addSegmentTimelineElementsToList( 1008 segmentTimeline, 1009 startTime, 1010 elementDuration, 1011 elementRepeatCount, 1012 /* endTime= */ newStartTime); 1013 } 1014 if (newStartTime != C.TIME_UNSET) { 1015 startTime = newStartTime; 1016 } 1017 elementDuration = parseLong(xpp, "d", C.TIME_UNSET); 1018 elementRepeatCount = parseInt(xpp, "r", 0); 1019 havePreviousTimelineElement = true; 1020 } else { 1021 maybeSkipTag(xpp); 1022 } 1023 } while (!XmlPullParserUtil.isEndTag(xpp, "SegmentTimeline")); 1024 if (havePreviousTimelineElement) { 1025 long periodDuration = Util.scaleLargeTimestamp(periodDurationMs, timescale, 1000); 1026 addSegmentTimelineElementsToList( 1027 segmentTimeline, 1028 startTime, 1029 elementDuration, 1030 elementRepeatCount, 1031 /* endTime= */ periodDuration); 1032 } 1033 return segmentTimeline; 1034 } 1035 1036 /** 1037 * Adds timeline elements for one S tag to the segment timeline. 1038 * 1039 * @param startTime Start time of the first timeline element. 1040 * @param elementDuration Duration of one timeline element. 1041 * @param elementRepeatCount Number of timeline elements minus one. May be negative to indicate 1042 * that the count is determined by the total duration and the element duration. 1043 * @param endTime End time of the last timeline element for this S tag, or {@link C#TIME_UNSET} if 1044 * unknown. Only needed if {@code repeatCount} is negative. 1045 * @return Calculated next start time. 1046 */ addSegmentTimelineElementsToList( List<SegmentTimelineElement> segmentTimeline, long startTime, long elementDuration, int elementRepeatCount, long endTime)1047 private long addSegmentTimelineElementsToList( 1048 List<SegmentTimelineElement> segmentTimeline, 1049 long startTime, 1050 long elementDuration, 1051 int elementRepeatCount, 1052 long endTime) { 1053 int count = 1054 elementRepeatCount >= 0 1055 ? 1 + elementRepeatCount 1056 : (int) Util.ceilDivide(endTime - startTime, elementDuration); 1057 for (int i = 0; i < count; i++) { 1058 segmentTimeline.add(buildSegmentTimelineElement(startTime, elementDuration)); 1059 startTime += elementDuration; 1060 } 1061 return startTime; 1062 } 1063 buildSegmentTimelineElement(long startTime, long duration)1064 protected SegmentTimelineElement buildSegmentTimelineElement(long startTime, long duration) { 1065 return new SegmentTimelineElement(startTime, duration); 1066 } 1067 1068 @Nullable parseUrlTemplate( XmlPullParser xpp, String name, @Nullable UrlTemplate defaultValue)1069 protected UrlTemplate parseUrlTemplate( 1070 XmlPullParser xpp, String name, @Nullable UrlTemplate defaultValue) { 1071 String valueString = xpp.getAttributeValue(null, name); 1072 if (valueString != null) { 1073 return UrlTemplate.compile(valueString); 1074 } 1075 return defaultValue; 1076 } 1077 parseInitialization(XmlPullParser xpp)1078 protected RangedUri parseInitialization(XmlPullParser xpp) { 1079 return parseRangedUrl(xpp, "sourceURL", "range"); 1080 } 1081 parseSegmentUrl(XmlPullParser xpp)1082 protected RangedUri parseSegmentUrl(XmlPullParser xpp) { 1083 return parseRangedUrl(xpp, "media", "mediaRange"); 1084 } 1085 parseRangedUrl(XmlPullParser xpp, String urlAttribute, String rangeAttribute)1086 protected RangedUri parseRangedUrl(XmlPullParser xpp, String urlAttribute, 1087 String rangeAttribute) { 1088 String urlText = xpp.getAttributeValue(null, urlAttribute); 1089 long rangeStart = 0; 1090 long rangeLength = C.LENGTH_UNSET; 1091 String rangeText = xpp.getAttributeValue(null, rangeAttribute); 1092 if (rangeText != null) { 1093 String[] rangeTextArray = rangeText.split("-"); 1094 rangeStart = Long.parseLong(rangeTextArray[0]); 1095 if (rangeTextArray.length == 2) { 1096 rangeLength = Long.parseLong(rangeTextArray[1]) - rangeStart + 1; 1097 } 1098 } 1099 return buildRangedUri(urlText, rangeStart, rangeLength); 1100 } 1101 buildRangedUri(String urlText, long rangeStart, long rangeLength)1102 protected RangedUri buildRangedUri(String urlText, long rangeStart, long rangeLength) { 1103 return new RangedUri(urlText, rangeStart, rangeLength); 1104 } 1105 parseProgramInformation(XmlPullParser xpp)1106 protected ProgramInformation parseProgramInformation(XmlPullParser xpp) 1107 throws IOException, XmlPullParserException { 1108 String title = null; 1109 String source = null; 1110 String copyright = null; 1111 String moreInformationURL = parseString(xpp, "moreInformationURL", null); 1112 String lang = parseString(xpp, "lang", null); 1113 do { 1114 xpp.next(); 1115 if (XmlPullParserUtil.isStartTag(xpp, "Title")) { 1116 title = xpp.nextText(); 1117 } else if (XmlPullParserUtil.isStartTag(xpp, "Source")) { 1118 source = xpp.nextText(); 1119 } else if (XmlPullParserUtil.isStartTag(xpp, "Copyright")) { 1120 copyright = xpp.nextText(); 1121 } else { 1122 maybeSkipTag(xpp); 1123 } 1124 } while (!XmlPullParserUtil.isEndTag(xpp, "ProgramInformation")); 1125 return new ProgramInformation(title, source, copyright, moreInformationURL, lang); 1126 } 1127 1128 /** 1129 * Parses a Label element. 1130 * 1131 * @param xpp The parser from which to read. 1132 * @throws XmlPullParserException If an error occurs parsing the element. 1133 * @throws IOException If an error occurs reading the element. 1134 * @return The parsed label. 1135 */ parseLabel(XmlPullParser xpp)1136 protected String parseLabel(XmlPullParser xpp) throws XmlPullParserException, IOException { 1137 return parseText(xpp, "Label"); 1138 } 1139 1140 /** 1141 * Parses a BaseURL element. 1142 * 1143 * @param xpp The parser from which to read. 1144 * @param parentBaseUrl A base URL for resolving the parsed URL. 1145 * @throws XmlPullParserException If an error occurs parsing the element. 1146 * @throws IOException If an error occurs reading the element. 1147 * @return The parsed and resolved URL. 1148 */ parseBaseUrl(XmlPullParser xpp, String parentBaseUrl)1149 protected String parseBaseUrl(XmlPullParser xpp, String parentBaseUrl) 1150 throws XmlPullParserException, IOException { 1151 return UriUtil.resolve(parentBaseUrl, parseText(xpp, "BaseURL")); 1152 } 1153 1154 // AudioChannelConfiguration parsing. 1155 parseAudioChannelConfiguration(XmlPullParser xpp)1156 protected int parseAudioChannelConfiguration(XmlPullParser xpp) 1157 throws XmlPullParserException, IOException { 1158 String schemeIdUri = parseString(xpp, "schemeIdUri", null); 1159 int audioChannels = 1160 "urn:mpeg:dash:23003:3:audio_channel_configuration:2011".equals(schemeIdUri) 1161 ? parseInt(xpp, "value", Format.NO_VALUE) 1162 : ("tag:dolby.com,2014:dash:audio_channel_configuration:2011".equals(schemeIdUri) 1163 || "urn:dolby:dash:audio_channel_configuration:2011".equals(schemeIdUri) 1164 ? parseDolbyChannelConfiguration(xpp) 1165 : Format.NO_VALUE); 1166 do { 1167 xpp.next(); 1168 } while (!XmlPullParserUtil.isEndTag(xpp, "AudioChannelConfiguration")); 1169 return audioChannels; 1170 } 1171 1172 // Selection flag parsing. 1173 parseSelectionFlagsFromRoleDescriptors(List<Descriptor> roleDescriptors)1174 protected int parseSelectionFlagsFromRoleDescriptors(List<Descriptor> roleDescriptors) { 1175 for (int i = 0; i < roleDescriptors.size(); i++) { 1176 Descriptor descriptor = roleDescriptors.get(i); 1177 if ("urn:mpeg:dash:role:2011".equalsIgnoreCase(descriptor.schemeIdUri) 1178 && "main".equals(descriptor.value)) { 1179 return C.SELECTION_FLAG_DEFAULT; 1180 } 1181 } 1182 return 0; 1183 } 1184 1185 // Role and Accessibility parsing. 1186 1187 @C.RoleFlags parseRoleFlagsFromRoleDescriptors(List<Descriptor> roleDescriptors)1188 protected int parseRoleFlagsFromRoleDescriptors(List<Descriptor> roleDescriptors) { 1189 @C.RoleFlags int result = 0; 1190 for (int i = 0; i < roleDescriptors.size(); i++) { 1191 Descriptor descriptor = roleDescriptors.get(i); 1192 if ("urn:mpeg:dash:role:2011".equalsIgnoreCase(descriptor.schemeIdUri)) { 1193 result |= parseDashRoleSchemeValue(descriptor.value); 1194 } 1195 } 1196 return result; 1197 } 1198 1199 @C.RoleFlags parseRoleFlagsFromAccessibilityDescriptors( List<Descriptor> accessibilityDescriptors)1200 protected int parseRoleFlagsFromAccessibilityDescriptors( 1201 List<Descriptor> accessibilityDescriptors) { 1202 @C.RoleFlags int result = 0; 1203 for (int i = 0; i < accessibilityDescriptors.size(); i++) { 1204 Descriptor descriptor = accessibilityDescriptors.get(i); 1205 if ("urn:mpeg:dash:role:2011".equalsIgnoreCase(descriptor.schemeIdUri)) { 1206 result |= parseDashRoleSchemeValue(descriptor.value); 1207 } else if ("urn:tva:metadata:cs:AudioPurposeCS:2007" 1208 .equalsIgnoreCase(descriptor.schemeIdUri)) { 1209 result |= parseTvaAudioPurposeCsValue(descriptor.value); 1210 } 1211 } 1212 return result; 1213 } 1214 1215 @C.RoleFlags parseRoleFlagsFromProperties(List<Descriptor> accessibilityDescriptors)1216 protected int parseRoleFlagsFromProperties(List<Descriptor> accessibilityDescriptors) { 1217 @C.RoleFlags int result = 0; 1218 for (int i = 0; i < accessibilityDescriptors.size(); i++) { 1219 Descriptor descriptor = accessibilityDescriptors.get(i); 1220 if ("http://dashif.org/guidelines/trickmode".equalsIgnoreCase(descriptor.schemeIdUri)) { 1221 result |= C.ROLE_FLAG_TRICK_PLAY; 1222 } 1223 } 1224 return result; 1225 } 1226 1227 @C.RoleFlags parseDashRoleSchemeValue(@ullable String value)1228 protected int parseDashRoleSchemeValue(@Nullable String value) { 1229 if (value == null) { 1230 return 0; 1231 } 1232 switch (value) { 1233 case "main": 1234 return C.ROLE_FLAG_MAIN; 1235 case "alternate": 1236 return C.ROLE_FLAG_ALTERNATE; 1237 case "supplementary": 1238 return C.ROLE_FLAG_SUPPLEMENTARY; 1239 case "commentary": 1240 return C.ROLE_FLAG_COMMENTARY; 1241 case "dub": 1242 return C.ROLE_FLAG_DUB; 1243 case "emergency": 1244 return C.ROLE_FLAG_EMERGENCY; 1245 case "caption": 1246 return C.ROLE_FLAG_CAPTION; 1247 case "subtitle": 1248 return C.ROLE_FLAG_SUBTITLE; 1249 case "sign": 1250 return C.ROLE_FLAG_SIGN; 1251 case "description": 1252 return C.ROLE_FLAG_DESCRIBES_VIDEO; 1253 case "enhanced-audio-intelligibility": 1254 return C.ROLE_FLAG_ENHANCED_DIALOG_INTELLIGIBILITY; 1255 default: 1256 return 0; 1257 } 1258 } 1259 1260 @C.RoleFlags parseTvaAudioPurposeCsValue(@ullable String value)1261 protected int parseTvaAudioPurposeCsValue(@Nullable String value) { 1262 if (value == null) { 1263 return 0; 1264 } 1265 switch (value) { 1266 case "1": // Audio description for the visually impaired. 1267 return C.ROLE_FLAG_DESCRIBES_VIDEO; 1268 case "2": // Audio description for the hard of hearing. 1269 return C.ROLE_FLAG_ENHANCED_DIALOG_INTELLIGIBILITY; 1270 case "3": // Supplemental commentary. 1271 return C.ROLE_FLAG_SUPPLEMENTARY; 1272 case "4": // Director's commentary. 1273 return C.ROLE_FLAG_COMMENTARY; 1274 case "6": // Main programme audio. 1275 return C.ROLE_FLAG_MAIN; 1276 default: 1277 return 0; 1278 } 1279 } 1280 1281 // Utility methods. 1282 1283 /** 1284 * If the provided {@link XmlPullParser} is currently positioned at the start of a tag, skips 1285 * forward to the end of that tag. 1286 * 1287 * @param xpp The {@link XmlPullParser}. 1288 * @throws XmlPullParserException If an error occurs parsing the stream. 1289 * @throws IOException If an error occurs reading the stream. 1290 */ maybeSkipTag(XmlPullParser xpp)1291 public static void maybeSkipTag(XmlPullParser xpp) throws IOException, XmlPullParserException { 1292 if (!XmlPullParserUtil.isStartTag(xpp)) { 1293 return; 1294 } 1295 int depth = 1; 1296 while (depth != 0) { 1297 xpp.next(); 1298 if (XmlPullParserUtil.isStartTag(xpp)) { 1299 depth++; 1300 } else if (XmlPullParserUtil.isEndTag(xpp)) { 1301 depth--; 1302 } 1303 } 1304 } 1305 1306 /** 1307 * Removes unnecessary {@link SchemeData}s with null {@link SchemeData#data}. 1308 */ filterRedundantIncompleteSchemeDatas(ArrayList<SchemeData> schemeDatas)1309 private static void filterRedundantIncompleteSchemeDatas(ArrayList<SchemeData> schemeDatas) { 1310 for (int i = schemeDatas.size() - 1; i >= 0; i--) { 1311 SchemeData schemeData = schemeDatas.get(i); 1312 if (!schemeData.hasData()) { 1313 for (int j = 0; j < schemeDatas.size(); j++) { 1314 if (schemeDatas.get(j).canReplace(schemeData)) { 1315 // schemeData is incomplete, but there is another matching SchemeData which does contain 1316 // data, so we remove the incomplete one. 1317 schemeDatas.remove(i); 1318 break; 1319 } 1320 } 1321 } 1322 } 1323 } 1324 1325 /** 1326 * Derives a sample mimeType from a container mimeType and codecs attribute. 1327 * 1328 * @param containerMimeType The mimeType of the container. 1329 * @param codecs The codecs attribute. 1330 * @return The derived sample mimeType, or null if it could not be derived. 1331 */ 1332 @Nullable getSampleMimeType( @ullable String containerMimeType, @Nullable String codecs)1333 private static String getSampleMimeType( 1334 @Nullable String containerMimeType, @Nullable String codecs) { 1335 if (MimeTypes.isAudio(containerMimeType)) { 1336 return MimeTypes.getAudioMediaMimeType(codecs); 1337 } else if (MimeTypes.isVideo(containerMimeType)) { 1338 return MimeTypes.getVideoMediaMimeType(codecs); 1339 } else if (MimeTypes.isText(containerMimeType)) { 1340 if (MimeTypes.APPLICATION_RAWCC.equals(containerMimeType)) { 1341 // RawCC is special because it's a text specific container format. 1342 return MimeTypes.getTextMediaMimeType(codecs); 1343 } 1344 // All other text types are raw formats. 1345 return containerMimeType; 1346 } else if (MimeTypes.APPLICATION_MP4.equals(containerMimeType)) { 1347 return MimeTypes.getMediaMimeType(codecs); 1348 } 1349 return null; 1350 } 1351 1352 /** 1353 * Checks two languages for consistency, returning the consistent language, or throwing an {@link 1354 * IllegalStateException} if the languages are inconsistent. 1355 * 1356 * <p>Two languages are consistent if they are equal, or if one is null. 1357 * 1358 * @param firstLanguage The first language. 1359 * @param secondLanguage The second language. 1360 * @return The consistent language. 1361 */ 1362 @Nullable checkLanguageConsistency( @ullable String firstLanguage, @Nullable String secondLanguage)1363 private static String checkLanguageConsistency( 1364 @Nullable String firstLanguage, @Nullable String secondLanguage) { 1365 if (firstLanguage == null) { 1366 return secondLanguage; 1367 } else if (secondLanguage == null) { 1368 return firstLanguage; 1369 } else { 1370 Assertions.checkState(firstLanguage.equals(secondLanguage)); 1371 return firstLanguage; 1372 } 1373 } 1374 1375 /** 1376 * Checks two adaptation set content types for consistency, returning the consistent type, or 1377 * throwing an {@link IllegalStateException} if the types are inconsistent. 1378 * <p> 1379 * Two types are consistent if they are equal, or if one is {@link C#TRACK_TYPE_UNKNOWN}. 1380 * Where one of the types is {@link C#TRACK_TYPE_UNKNOWN}, the other is returned. 1381 * 1382 * @param firstType The first type. 1383 * @param secondType The second type. 1384 * @return The consistent type. 1385 */ checkContentTypeConsistency(int firstType, int secondType)1386 private static int checkContentTypeConsistency(int firstType, int secondType) { 1387 if (firstType == C.TRACK_TYPE_UNKNOWN) { 1388 return secondType; 1389 } else if (secondType == C.TRACK_TYPE_UNKNOWN) { 1390 return firstType; 1391 } else { 1392 Assertions.checkState(firstType == secondType); 1393 return firstType; 1394 } 1395 } 1396 1397 /** 1398 * Parses a {@link Descriptor} from an element. 1399 * 1400 * @param xpp The parser from which to read. 1401 * @param tag The tag of the element being parsed. 1402 * @throws XmlPullParserException If an error occurs parsing the element. 1403 * @throws IOException If an error occurs reading the element. 1404 * @return The parsed {@link Descriptor}. 1405 */ parseDescriptor(XmlPullParser xpp, String tag)1406 protected static Descriptor parseDescriptor(XmlPullParser xpp, String tag) 1407 throws XmlPullParserException, IOException { 1408 String schemeIdUri = parseString(xpp, "schemeIdUri", ""); 1409 String value = parseString(xpp, "value", null); 1410 String id = parseString(xpp, "id", null); 1411 do { 1412 xpp.next(); 1413 } while (!XmlPullParserUtil.isEndTag(xpp, tag)); 1414 return new Descriptor(schemeIdUri, value, id); 1415 } 1416 parseCea608AccessibilityChannel( List<Descriptor> accessibilityDescriptors)1417 protected static int parseCea608AccessibilityChannel( 1418 List<Descriptor> accessibilityDescriptors) { 1419 for (int i = 0; i < accessibilityDescriptors.size(); i++) { 1420 Descriptor descriptor = accessibilityDescriptors.get(i); 1421 if ("urn:scte:dash:cc:cea-608:2015".equals(descriptor.schemeIdUri) 1422 && descriptor.value != null) { 1423 Matcher accessibilityValueMatcher = CEA_608_ACCESSIBILITY_PATTERN.matcher(descriptor.value); 1424 if (accessibilityValueMatcher.matches()) { 1425 return Integer.parseInt(accessibilityValueMatcher.group(1)); 1426 } else { 1427 Log.w(TAG, "Unable to parse CEA-608 channel number from: " + descriptor.value); 1428 } 1429 } 1430 } 1431 return Format.NO_VALUE; 1432 } 1433 parseCea708AccessibilityChannel( List<Descriptor> accessibilityDescriptors)1434 protected static int parseCea708AccessibilityChannel( 1435 List<Descriptor> accessibilityDescriptors) { 1436 for (int i = 0; i < accessibilityDescriptors.size(); i++) { 1437 Descriptor descriptor = accessibilityDescriptors.get(i); 1438 if ("urn:scte:dash:cc:cea-708:2015".equals(descriptor.schemeIdUri) 1439 && descriptor.value != null) { 1440 Matcher accessibilityValueMatcher = CEA_708_ACCESSIBILITY_PATTERN.matcher(descriptor.value); 1441 if (accessibilityValueMatcher.matches()) { 1442 return Integer.parseInt(accessibilityValueMatcher.group(1)); 1443 } else { 1444 Log.w(TAG, "Unable to parse CEA-708 service block number from: " + descriptor.value); 1445 } 1446 } 1447 } 1448 return Format.NO_VALUE; 1449 } 1450 parseEac3SupplementalProperties(List<Descriptor> supplementalProperties)1451 protected static String parseEac3SupplementalProperties(List<Descriptor> supplementalProperties) { 1452 for (int i = 0; i < supplementalProperties.size(); i++) { 1453 Descriptor descriptor = supplementalProperties.get(i); 1454 String schemeIdUri = descriptor.schemeIdUri; 1455 if (("tag:dolby.com,2018:dash:EC3_ExtensionType:2018".equals(schemeIdUri) 1456 && "JOC".equals(descriptor.value)) 1457 || ("tag:dolby.com,2014:dash:DolbyDigitalPlusExtensionType:2014".equals(schemeIdUri) 1458 && "ec+3".equals(descriptor.value))) { 1459 return MimeTypes.AUDIO_E_AC3_JOC; 1460 } 1461 } 1462 return MimeTypes.AUDIO_E_AC3; 1463 } 1464 parseFrameRate(XmlPullParser xpp, float defaultValue)1465 protected static float parseFrameRate(XmlPullParser xpp, float defaultValue) { 1466 float frameRate = defaultValue; 1467 String frameRateAttribute = xpp.getAttributeValue(null, "frameRate"); 1468 if (frameRateAttribute != null) { 1469 Matcher frameRateMatcher = FRAME_RATE_PATTERN.matcher(frameRateAttribute); 1470 if (frameRateMatcher.matches()) { 1471 int numerator = Integer.parseInt(frameRateMatcher.group(1)); 1472 String denominatorString = frameRateMatcher.group(2); 1473 if (!TextUtils.isEmpty(denominatorString)) { 1474 frameRate = (float) numerator / Integer.parseInt(denominatorString); 1475 } else { 1476 frameRate = numerator; 1477 } 1478 } 1479 } 1480 return frameRate; 1481 } 1482 parseDuration(XmlPullParser xpp, String name, long defaultValue)1483 protected static long parseDuration(XmlPullParser xpp, String name, long defaultValue) { 1484 String value = xpp.getAttributeValue(null, name); 1485 if (value == null) { 1486 return defaultValue; 1487 } else { 1488 return Util.parseXsDuration(value); 1489 } 1490 } 1491 parseDateTime(XmlPullParser xpp, String name, long defaultValue)1492 protected static long parseDateTime(XmlPullParser xpp, String name, long defaultValue) 1493 throws ParserException { 1494 String value = xpp.getAttributeValue(null, name); 1495 if (value == null) { 1496 return defaultValue; 1497 } else { 1498 return Util.parseXsDateTime(value); 1499 } 1500 } 1501 parseText(XmlPullParser xpp, String label)1502 protected static String parseText(XmlPullParser xpp, String label) 1503 throws XmlPullParserException, IOException { 1504 String text = ""; 1505 do { 1506 xpp.next(); 1507 if (xpp.getEventType() == XmlPullParser.TEXT) { 1508 text = xpp.getText(); 1509 } else { 1510 maybeSkipTag(xpp); 1511 } 1512 } while (!XmlPullParserUtil.isEndTag(xpp, label)); 1513 return text; 1514 } 1515 parseInt(XmlPullParser xpp, String name, int defaultValue)1516 protected static int parseInt(XmlPullParser xpp, String name, int defaultValue) { 1517 String value = xpp.getAttributeValue(null, name); 1518 return value == null ? defaultValue : Integer.parseInt(value); 1519 } 1520 parseLong(XmlPullParser xpp, String name, long defaultValue)1521 protected static long parseLong(XmlPullParser xpp, String name, long defaultValue) { 1522 String value = xpp.getAttributeValue(null, name); 1523 return value == null ? defaultValue : Long.parseLong(value); 1524 } 1525 parseString(XmlPullParser xpp, String name, String defaultValue)1526 protected static String parseString(XmlPullParser xpp, String name, String defaultValue) { 1527 String value = xpp.getAttributeValue(null, name); 1528 return value == null ? defaultValue : value; 1529 } 1530 1531 /** 1532 * Parses the number of channels from the value attribute of an AudioElementConfiguration with 1533 * schemeIdUri "tag:dolby.com,2014:dash:audio_channel_configuration:2011", as defined by table E.5 1534 * in ETSI TS 102 366, or the legacy schemeIdUri 1535 * "urn:dolby:dash:audio_channel_configuration:2011". 1536 * 1537 * @param xpp The parser from which to read. 1538 * @return The parsed number of channels, or {@link Format#NO_VALUE} if the channel count could 1539 * not be parsed. 1540 */ parseDolbyChannelConfiguration(XmlPullParser xpp)1541 protected static int parseDolbyChannelConfiguration(XmlPullParser xpp) { 1542 String value = Util.toLowerInvariant(xpp.getAttributeValue(null, "value")); 1543 if (value == null) { 1544 return Format.NO_VALUE; 1545 } 1546 switch (value) { 1547 case "4000": 1548 return 1; 1549 case "a000": 1550 return 2; 1551 case "f801": 1552 return 6; 1553 case "fa01": 1554 return 8; 1555 default: 1556 return Format.NO_VALUE; 1557 } 1558 } 1559 parseLastSegmentNumberSupplementalProperty( List<Descriptor> supplementalProperties)1560 protected static long parseLastSegmentNumberSupplementalProperty( 1561 List<Descriptor> supplementalProperties) { 1562 for (int i = 0; i < supplementalProperties.size(); i++) { 1563 Descriptor descriptor = supplementalProperties.get(i); 1564 if ("http://dashif.org/guidelines/last-segment-number" 1565 .equalsIgnoreCase(descriptor.schemeIdUri)) { 1566 return Long.parseLong(descriptor.value); 1567 } 1568 } 1569 return C.INDEX_UNSET; 1570 } 1571 1572 /** A parsed Representation element. */ 1573 protected static final class RepresentationInfo { 1574 1575 public final Format format; 1576 public final String baseUrl; 1577 public final SegmentBase segmentBase; 1578 @Nullable public final String drmSchemeType; 1579 public final ArrayList<SchemeData> drmSchemeDatas; 1580 public final ArrayList<Descriptor> inbandEventStreams; 1581 public final long revisionId; 1582 RepresentationInfo( Format format, String baseUrl, SegmentBase segmentBase, @Nullable String drmSchemeType, ArrayList<SchemeData> drmSchemeDatas, ArrayList<Descriptor> inbandEventStreams, long revisionId)1583 public RepresentationInfo( 1584 Format format, 1585 String baseUrl, 1586 SegmentBase segmentBase, 1587 @Nullable String drmSchemeType, 1588 ArrayList<SchemeData> drmSchemeDatas, 1589 ArrayList<Descriptor> inbandEventStreams, 1590 long revisionId) { 1591 this.format = format; 1592 this.baseUrl = baseUrl; 1593 this.segmentBase = segmentBase; 1594 this.drmSchemeType = drmSchemeType; 1595 this.drmSchemeDatas = drmSchemeDatas; 1596 this.inbandEventStreams = inbandEventStreams; 1597 this.revisionId = revisionId; 1598 } 1599 1600 } 1601 1602 } 1603