• 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 java.util.Locale;
19 
20 /**
21  * A template from which URLs can be built.
22  * <p>
23  * URLs are built according to the substitution rules defined in ISO/IEC 23009-1:2014 5.3.9.4.4.
24  */
25 public final class UrlTemplate {
26 
27   private static final String REPRESENTATION = "RepresentationID";
28   private static final String NUMBER = "Number";
29   private static final String BANDWIDTH = "Bandwidth";
30   private static final String TIME = "Time";
31   private static final String ESCAPED_DOLLAR = "$$";
32   private static final String DEFAULT_FORMAT_TAG = "%01d";
33 
34   private static final int REPRESENTATION_ID = 1;
35   private static final int NUMBER_ID = 2;
36   private static final int BANDWIDTH_ID = 3;
37   private static final int TIME_ID = 4;
38 
39   private final String[] urlPieces;
40   private final int[] identifiers;
41   private final String[] identifierFormatTags;
42   private final int identifierCount;
43 
44   /**
45    * Compile an instance from the provided template string.
46    *
47    * @param template The template.
48    * @return The compiled instance.
49    * @throws IllegalArgumentException If the template string is malformed.
50    */
compile(String template)51   public static UrlTemplate compile(String template) {
52     // These arrays are sizes assuming each of the four possible identifiers will be present at
53     // most once in the template, which seems like a reasonable assumption.
54     String[] urlPieces = new String[5];
55     int[] identifiers = new int[4];
56     String[] identifierFormatTags = new String[4];
57     int identifierCount = parseTemplate(template, urlPieces, identifiers, identifierFormatTags);
58     return new UrlTemplate(urlPieces, identifiers, identifierFormatTags, identifierCount);
59   }
60 
61   /**
62    * Internal constructor. Use {@link #compile(String)} to build instances of this class.
63    */
UrlTemplate(String[] urlPieces, int[] identifiers, String[] identifierFormatTags, int identifierCount)64   private UrlTemplate(String[] urlPieces, int[] identifiers, String[] identifierFormatTags,
65       int identifierCount) {
66     this.urlPieces = urlPieces;
67     this.identifiers = identifiers;
68     this.identifierFormatTags = identifierFormatTags;
69     this.identifierCount = identifierCount;
70   }
71 
72   /**
73    * Constructs a Uri from the template, substituting in the provided arguments.
74    *
75    * <p>Arguments whose corresponding identifiers are not present in the template will be ignored.
76    *
77    * @param representationId The representation identifier.
78    * @param segmentNumber The segment number.
79    * @param bandwidth The bandwidth.
80    * @param time The time as specified by the segment timeline.
81    * @return The built Uri.
82    */
buildUri(String representationId, long segmentNumber, int bandwidth, long time)83   public String buildUri(String representationId, long segmentNumber, int bandwidth, long time) {
84     StringBuilder builder = new StringBuilder();
85     for (int i = 0; i < identifierCount; i++) {
86       builder.append(urlPieces[i]);
87       if (identifiers[i] == REPRESENTATION_ID) {
88         builder.append(representationId);
89       } else if (identifiers[i] == NUMBER_ID) {
90         builder.append(String.format(Locale.US, identifierFormatTags[i], segmentNumber));
91       } else if (identifiers[i] == BANDWIDTH_ID) {
92         builder.append(String.format(Locale.US, identifierFormatTags[i], bandwidth));
93       } else if (identifiers[i] == TIME_ID) {
94         builder.append(String.format(Locale.US, identifierFormatTags[i], time));
95       }
96     }
97     builder.append(urlPieces[identifierCount]);
98     return builder.toString();
99   }
100 
101   /**
102    * Parses {@code template}, placing the decomposed components into the provided arrays.
103    * <p>
104    * If the return value is N, {@code urlPieces} will contain (N+1) strings that must be
105    * interleaved with N arguments in order to construct a url. The N identifiers that correspond to
106    * the required arguments, together with the tags that define their required formatting, are
107    * returned in {@code identifiers} and {@code identifierFormatTags} respectively.
108    *
109    * @param template The template to parse.
110    * @param urlPieces A holder for pieces of url parsed from the template.
111    * @param identifiers A holder for identifiers parsed from the template.
112    * @param identifierFormatTags A holder for format tags corresponding to the parsed identifiers.
113    * @return The number of identifiers in the template url.
114    * @throws IllegalArgumentException If the template string is malformed.
115    */
parseTemplate(String template, String[] urlPieces, int[] identifiers, String[] identifierFormatTags)116   private static int parseTemplate(String template, String[] urlPieces, int[] identifiers,
117       String[] identifierFormatTags) {
118     urlPieces[0] = "";
119     int templateIndex = 0;
120     int identifierCount = 0;
121     while (templateIndex < template.length()) {
122       int dollarIndex = template.indexOf("$", templateIndex);
123       if (dollarIndex == -1) {
124         urlPieces[identifierCount] += template.substring(templateIndex);
125         templateIndex = template.length();
126       } else if (dollarIndex != templateIndex) {
127         urlPieces[identifierCount] += template.substring(templateIndex, dollarIndex);
128         templateIndex = dollarIndex;
129       } else if (template.startsWith(ESCAPED_DOLLAR, templateIndex)) {
130         urlPieces[identifierCount] += "$";
131         templateIndex += 2;
132       } else {
133         int secondIndex = template.indexOf("$", templateIndex + 1);
134         String identifier = template.substring(templateIndex + 1, secondIndex);
135         if (identifier.equals(REPRESENTATION)) {
136           identifiers[identifierCount] = REPRESENTATION_ID;
137         } else {
138           int formatTagIndex = identifier.indexOf("%0");
139           String formatTag = DEFAULT_FORMAT_TAG;
140           if (formatTagIndex != -1) {
141             formatTag = identifier.substring(formatTagIndex);
142             // Allowed conversions are decimal integer (which is the only conversion allowed by the
143             // DASH specification) and hexadecimal integer (due to existing content that uses it).
144             // Else we assume that the conversion is missing, and that it should be decimal integer.
145             if (!formatTag.endsWith("d") && !formatTag.endsWith("x")) {
146               formatTag += "d";
147             }
148             identifier = identifier.substring(0, formatTagIndex);
149           }
150           switch (identifier) {
151             case NUMBER:
152               identifiers[identifierCount] = NUMBER_ID;
153               break;
154             case BANDWIDTH:
155               identifiers[identifierCount] = BANDWIDTH_ID;
156               break;
157             case TIME:
158               identifiers[identifierCount] = TIME_ID;
159               break;
160             default:
161               throw new IllegalArgumentException("Invalid template: " + template);
162           }
163           identifierFormatTags[identifierCount] = formatTag;
164         }
165         identifierCount++;
166         urlPieces[identifierCount] = "";
167         templateIndex = secondIndex + 1;
168       }
169     }
170     return identifierCount;
171   }
172 
173 }
174