diff --git a/library/src/main/java/com/google/android/exoplayer/dash/mpd/RangedUri.java b/library/src/main/java/com/google/android/exoplayer/dash/mpd/RangedUri.java new file mode 100644 index 0000000000..43f52c0108 --- /dev/null +++ b/library/src/main/java/com/google/android/exoplayer/dash/mpd/RangedUri.java @@ -0,0 +1,85 @@ +/* + * Copyright (C) 2014 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.google.android.exoplayer.dash.mpd; + +import com.google.android.exoplayer.util.Assertions; + +import android.net.Uri; + +/** + * Defines a range of data located at a {@link Uri}. + */ +public final class RangedUri { + + /** + * The (zero based) index of the first byte of the range. + */ + public final long start; + + /** + * The length of the range, or -1 to indicate that the range is unbounded. + */ + public final long length; + + // The {@link Uri} is stored internally in two parts, {@link #baseUri} and {@link uriString}. + // This helps optimize memory usage in the same way that DASH manifests allow many URLs to be + // expressed concisely in the form of a single BaseURL and many relative paths. Note that this + // optimization relies on the same {@code Uri} being passed as the {@link #baseUri} to many + // instances of this class. + private final Uri baseUri; + private final String stringUri; + + /** + * Constructs an ranged uri. + *

+ * The uri is built according to the following rules: + *

+ * + * @param baseUri An uri that can form the base of the uri defined by the instance. + * @param stringUri A relative or absolute uri in string form. + * @param start The (zero based) index of the first byte of the range. + * @param length The length of the range, or -1 to indicate that the range is unbounded. + */ + public RangedUri(Uri baseUri, String stringUri, long start, long length) { + Assertions.checkArgument(baseUri != null || stringUri != null); + this.baseUri = baseUri; + this.stringUri = stringUri; + this.start = start; + this.length = length; + } + + /** + * Returns the {@link Uri} represented by the instance. + * + * @return The {@link Uri} represented by the instance. + */ + public Uri getUri() { + if (stringUri == null) { + return baseUri; + } + Uri uri = Uri.parse(stringUri); + if (!uri.isAbsolute() && baseUri != null) { + uri = Uri.withAppendedPath(baseUri, stringUri); + } + return uri; + } + +} diff --git a/library/src/main/java/com/google/android/exoplayer/dash/mpd/UrlTemplate.java b/library/src/main/java/com/google/android/exoplayer/dash/mpd/UrlTemplate.java new file mode 100644 index 0000000000..c055b460b2 --- /dev/null +++ b/library/src/main/java/com/google/android/exoplayer/dash/mpd/UrlTemplate.java @@ -0,0 +1,164 @@ +/* + * Copyright (C) 2014 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.google.android.exoplayer.dash.mpd; + +/** + * A template from which URLs can be built. + *

+ * URLs are built according to the substitution rules defined in ISO/IEC 23009-1:2014 5.3.9.4.4. + */ +public final class UrlTemplate { + + private static final String REPRESENTATION = "RepresentationID"; + private static final String NUMBER = "Number"; + private static final String BANDWIDTH = "Bandwidth"; + private static final String TIME = "Time"; + private static final String ESCAPED_DOLLAR = "$$"; + private static final String DEFAULT_FORMAT_TAG = "%01d"; + + private static final int REPRESENTATION_ID = 1; + private static final int NUMBER_ID = 2; + private static final int BANDWIDTH_ID = 3; + private static final int TIME_ID = 4; + + private final String[] urlPieces; + private final int[] identifiers; + private final String[] identifierFormatTags; + private final int identifierCount; + + /** + * Compile an instance from the provided template string. + * + * @param template The template. + * @return The compiled instance. + * @throws IllegalArgumentException If the template string is malformed. + */ + public static UrlTemplate compile(String template) { + // These arrays are sizes assuming each of the four possible identifiers will be present at + // most once in the template, which seems like a reasonable assumption. + String[] urlPieces = new String[5]; + int[] identifiers = new int[4]; + String[] identifierFormatTags = new String[4]; + int identifierCount = parseTemplate(template, urlPieces, identifiers, identifierFormatTags); + return new UrlTemplate(urlPieces, identifiers, identifierFormatTags, identifierCount); + } + + /** + * Internal constructor. Use {@link #compile(String)} to build instances of this class. + */ + private UrlTemplate(String[] urlPieces, int[] identifiers, String[] identifierFormatTags, + int identifierCount) { + this.urlPieces = urlPieces; + this.identifiers = identifiers; + this.identifierFormatTags = identifierFormatTags; + this.identifierCount = identifierCount; + } + + /** + * Constructs a Uri from the template, substituting in the provided arguments. + *

+ * Arguments whose corresponding identifiers are not present in the template will be ignored. + * + * @param representationId The representation identifier. + * @param segmentNumber The segment number. + * @param bandwidth The bandwidth. + * @param time The time as specified by the segment timeline. + * @return The built Uri. + */ + public String buildUri(String representationId, int segmentNumber, int bandwidth, long time) { + StringBuilder builder = new StringBuilder(); + for (int i = 0; i < identifierCount; i++) { + builder.append(urlPieces[i]); + if (identifiers[i] == REPRESENTATION_ID) { + builder.append(representationId); + } else if (identifiers[i] == NUMBER_ID) { + builder.append(String.format(identifierFormatTags[i], segmentNumber)); + } else if (identifiers[i] == BANDWIDTH_ID) { + builder.append(String.format(identifierFormatTags[i], bandwidth)); + } else if (identifiers[i] == TIME_ID) { + builder.append(String.format(identifierFormatTags[i], time)); + } + } + builder.append(urlPieces[identifierCount]); + return builder.toString(); + } + + /** + * Parses {@code template}, placing the decomposed components into the provided arrays. + *

+ * If the return value is N, {@code urlPieces} will contain (N+1) strings that must be + * interleaved with N arguments in order to construct a url. The N identifiers that correspond to + * the required arguments, together with the tags that define their required formatting, are + * returned in {@code identifiers} and {@code identifierFormatTags} respectively. + * + * @param template The template to parse. + * @param urlPieces A holder for pieces of url parsed from the template. + * @param identifiers A holder for identifiers parsed from the template. + * @param identifierFormatTags A holder for format tags corresponding to the parsed identifiers. + * @return The number of identifiers in the template url. + * @throws IllegalArgumentException If the template string is malformed. + */ + private static int parseTemplate(String template, String[] urlPieces, int[] identifiers, + String[] identifierFormatTags) { + urlPieces[0] = ""; + int templateIndex = 0; + int identifierCount = 0; + while (templateIndex < template.length()) { + int dollarIndex = template.indexOf("$", templateIndex); + if (dollarIndex == -1) { + urlPieces[identifierCount] += template.substring(templateIndex); + templateIndex = template.length(); + } else if (dollarIndex != templateIndex) { + urlPieces[identifierCount] += template.substring(templateIndex, dollarIndex); + templateIndex = dollarIndex; + } else if (template.startsWith(ESCAPED_DOLLAR, templateIndex)) { + urlPieces[identifierCount] += "$"; + templateIndex += 2; + } else { + int secondIndex = template.indexOf("$", templateIndex + 1); + String identifier = template.substring(templateIndex + 1, secondIndex); + if (identifier.equals(REPRESENTATION)) { + identifiers[identifierCount] = REPRESENTATION_ID; + } else { + int formatTagIndex = identifier.indexOf("%0"); + String formatTag = DEFAULT_FORMAT_TAG; + if (formatTagIndex != -1) { + formatTag = identifier.substring(formatTagIndex); + if (!formatTag.endsWith("d")) { + formatTag += "d"; + } + identifier = identifier.substring(0, formatTagIndex); + } + if (identifier.equals(NUMBER)) { + identifiers[identifierCount] = NUMBER_ID; + } else if (identifier.equals(BANDWIDTH)) { + identifiers[identifierCount] = BANDWIDTH_ID; + } else if (identifier.equals(TIME)) { + identifiers[identifierCount] = TIME_ID; + } else { + throw new IllegalArgumentException("Invalid template: " + template); + } + identifierFormatTags[identifierCount] = formatTag; + } + identifierCount++; + urlPieces[identifierCount] = ""; + templateIndex = secondIndex + 1; + } + } + return identifierCount; + } + +} diff --git a/library/src/main/java/com/google/android/exoplayer/parser/webm/EbmlEventHandler.java b/library/src/main/java/com/google/android/exoplayer/parser/webm/EbmlEventHandler.java index 1ddb51c589..42e26d4531 100644 --- a/library/src/main/java/com/google/android/exoplayer/parser/webm/EbmlEventHandler.java +++ b/library/src/main/java/com/google/android/exoplayer/parser/webm/EbmlEventHandler.java @@ -103,6 +103,7 @@ import java.nio.ByteBuffer; *

  • {@link EbmlReader#readBytes(NonBlockingInputStream, ByteBuffer, int)}. *
  • {@link EbmlReader#skipBytes(NonBlockingInputStream, int)}. *
  • {@link EbmlReader#getBytesRead()}. + * * * @param id The integer ID of this element * @param elementOffsetBytes The byte offset where this element starts