• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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