mirror of
https://github.com/samsonjs/media.git
synced 2026-04-10 12:05:47 +00:00
commit
5dedf5d930
66 changed files with 3669 additions and 231 deletions
|
|
@ -26,6 +26,8 @@ import com.google.android.exoplayer.demo.player.DemoPlayer.RendererBuilder;
|
|||
import com.google.android.exoplayer.demo.player.HlsRendererBuilder;
|
||||
import com.google.android.exoplayer.demo.player.SmoothStreamingRendererBuilder;
|
||||
import com.google.android.exoplayer.demo.player.UnsupportedDrmException;
|
||||
import com.google.android.exoplayer.metadata.GeobMetadata;
|
||||
import com.google.android.exoplayer.metadata.PrivMetadata;
|
||||
import com.google.android.exoplayer.metadata.TxxxMetadata;
|
||||
import com.google.android.exoplayer.text.CaptionStyleCompat;
|
||||
import com.google.android.exoplayer.text.SubtitleView;
|
||||
|
|
@ -446,11 +448,22 @@ public class PlayerActivity extends Activity implements SurfaceHolder.Callback,
|
|||
|
||||
@Override
|
||||
public void onId3Metadata(Map<String, Object> metadata) {
|
||||
for (int i = 0; i < metadata.size(); i++) {
|
||||
if (metadata.containsKey(TxxxMetadata.TYPE)) {
|
||||
TxxxMetadata txxxMetadata = (TxxxMetadata) metadata.get(TxxxMetadata.TYPE);
|
||||
Log.i(TAG, String.format("ID3 TimedMetadata: description=%s, value=%s",
|
||||
txxxMetadata.description, txxxMetadata.value));
|
||||
for (Map.Entry<String, Object> entry : metadata.entrySet()) {
|
||||
if (TxxxMetadata.TYPE.equals(entry.getKey())) {
|
||||
TxxxMetadata txxxMetadata = (TxxxMetadata) entry.getValue();
|
||||
Log.i(TAG, String.format("ID3 TimedMetadata %s: description=%s, value=%s",
|
||||
TxxxMetadata.TYPE, txxxMetadata.description, txxxMetadata.value));
|
||||
} else if (PrivMetadata.TYPE.equals(entry.getKey())) {
|
||||
PrivMetadata privMetadata = (PrivMetadata) entry.getValue();
|
||||
Log.i(TAG, String.format("ID3 TimedMetadata %s: owner=%s",
|
||||
PrivMetadata.TYPE, privMetadata.owner));
|
||||
} else if (GeobMetadata.TYPE.equals(entry.getKey())) {
|
||||
GeobMetadata geobMetadata = (GeobMetadata) entry.getValue();
|
||||
Log.i(TAG, String.format("ID3 TimedMetadata %s: mimeType=%s, filename=%s, description=%s",
|
||||
GeobMetadata.TYPE, geobMetadata.mimeType, geobMetadata.filename,
|
||||
geobMetadata.description));
|
||||
} else {
|
||||
Log.i(TAG, String.format("ID3 TimedMetadata %s", entry.getKey()));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -123,6 +123,8 @@ import java.util.Locale;
|
|||
new Sample("Apple AAC media playlist",
|
||||
"https://devimages.apple.com.edgekey.net/streaming/examples/bipbop_4x3/gear0/"
|
||||
+ "prog_index.m3u8", DemoUtil.TYPE_HLS),
|
||||
new Sample("Apple ID3 metadata", "http://devimages.apple.com/samplecode/adDemo/ad.m3u8",
|
||||
DemoUtil.TYPE_HLS),
|
||||
};
|
||||
|
||||
public static final Sample[] MISC = new Sample[] {
|
||||
|
|
|
|||
|
|
@ -370,6 +370,7 @@ public abstract class MediaCodecTrackRenderer extends TrackRenderer {
|
|||
codecHotswapTimeMs = -1;
|
||||
inputIndex = -1;
|
||||
outputIndex = -1;
|
||||
waitingForKeys = false;
|
||||
decodeOnlyPresentationTimestamps.clear();
|
||||
inputBuffers = null;
|
||||
outputBuffers = null;
|
||||
|
|
@ -418,7 +419,6 @@ public abstract class MediaCodecTrackRenderer extends TrackRenderer {
|
|||
sourceState = SOURCE_STATE_NOT_READY;
|
||||
inputStreamEnded = false;
|
||||
outputStreamEnded = false;
|
||||
waitingForKeys = false;
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
@ -478,6 +478,7 @@ public abstract class MediaCodecTrackRenderer extends TrackRenderer {
|
|||
inputIndex = -1;
|
||||
outputIndex = -1;
|
||||
waitingForFirstSyncFrame = true;
|
||||
waitingForKeys = false;
|
||||
decodeOnlyPresentationTimestamps.clear();
|
||||
// Workaround for framework bugs.
|
||||
// See [Internal: b/8347958], [Internal: b/8578467], [Internal: b/8543366].
|
||||
|
|
|
|||
|
|
@ -581,7 +581,7 @@ public class DashChunkSource implements ChunkSource {
|
|||
}
|
||||
if ((result & Extractor.RESULT_READ_INDEX) != 0) {
|
||||
representationHolders.get(format.id).segmentIndex =
|
||||
new DashWrappingSegmentIndex(extractor.getIndex(), uri, indexAnchor);
|
||||
new DashWrappingSegmentIndex(extractor.getIndex(), uri.toString(), indexAnchor);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -19,8 +19,6 @@ import com.google.android.exoplayer.chunk.parser.SegmentIndex;
|
|||
import com.google.android.exoplayer.dash.mpd.RangedUri;
|
||||
import com.google.android.exoplayer.util.Util;
|
||||
|
||||
import android.net.Uri;
|
||||
|
||||
/**
|
||||
* An implementation of {@link DashSegmentIndex} that wraps a {@link SegmentIndex} parsed from a
|
||||
* media stream.
|
||||
|
|
@ -28,16 +26,16 @@ import android.net.Uri;
|
|||
public class DashWrappingSegmentIndex implements DashSegmentIndex {
|
||||
|
||||
private final SegmentIndex segmentIndex;
|
||||
private final Uri uri;
|
||||
private final String uri;
|
||||
private final long indexAnchor;
|
||||
|
||||
/**
|
||||
* @param segmentIndex The {@link SegmentIndex} to wrap.
|
||||
* @param uri The {@link Uri} where the data is located.
|
||||
* @param uri The URI where the data is located.
|
||||
* @param indexAnchor The index anchor point. This value is added to the byte offsets specified
|
||||
* in the wrapped {@link SegmentIndex}.
|
||||
*/
|
||||
public DashWrappingSegmentIndex(SegmentIndex segmentIndex, Uri uri, long indexAnchor) {
|
||||
public DashWrappingSegmentIndex(SegmentIndex segmentIndex, String uri, long indexAnchor) {
|
||||
this.segmentIndex = segmentIndex;
|
||||
this.uri = uri;
|
||||
this.indexAnchor = indexAnchor;
|
||||
|
|
|
|||
|
|
@ -24,9 +24,9 @@ import com.google.android.exoplayer.dash.mpd.SegmentBase.SingleSegmentBase;
|
|||
import com.google.android.exoplayer.upstream.NetworkLoadable;
|
||||
import com.google.android.exoplayer.util.Assertions;
|
||||
import com.google.android.exoplayer.util.MimeTypes;
|
||||
import com.google.android.exoplayer.util.UriUtil;
|
||||
import com.google.android.exoplayer.util.Util;
|
||||
|
||||
import android.net.Uri;
|
||||
import android.text.TextUtils;
|
||||
|
||||
import org.xml.sax.helpers.DefaultHandler;
|
||||
|
|
@ -83,7 +83,7 @@ public class MediaPresentationDescriptionParser extends DefaultHandler
|
|||
throw new ParserException(
|
||||
"inputStream does not contain a valid media presentation description");
|
||||
}
|
||||
return parseMediaPresentationDescription(xpp, Util.parseBaseUri(connectionUrl));
|
||||
return parseMediaPresentationDescription(xpp, connectionUrl);
|
||||
} catch (XmlPullParserException e) {
|
||||
throw new ParserException(e);
|
||||
} catch (ParseException e) {
|
||||
|
|
@ -92,7 +92,7 @@ public class MediaPresentationDescriptionParser extends DefaultHandler
|
|||
}
|
||||
|
||||
protected MediaPresentationDescription parseMediaPresentationDescription(XmlPullParser xpp,
|
||||
Uri baseUrl) throws XmlPullParserException, IOException, ParseException {
|
||||
String baseUrl) throws XmlPullParserException, IOException, ParseException {
|
||||
long availabilityStartTime = parseDateTime(xpp, "availabilityStartTime", -1);
|
||||
long durationMs = parseDuration(xpp, "mediaPresentationDuration", -1);
|
||||
long minBufferTimeMs = parseDuration(xpp, "minBufferTime", -1);
|
||||
|
|
@ -137,7 +137,7 @@ public class MediaPresentationDescriptionParser extends DefaultHandler
|
|||
return new UtcTimingElement(schemeIdUri, value);
|
||||
}
|
||||
|
||||
protected Period parsePeriod(XmlPullParser xpp, Uri baseUrl, long mpdDurationMs)
|
||||
protected Period parsePeriod(XmlPullParser xpp, String baseUrl, long mpdDurationMs)
|
||||
throws XmlPullParserException, IOException {
|
||||
String id = xpp.getAttributeValue(null, "id");
|
||||
long startMs = parseDuration(xpp, "start", 0);
|
||||
|
|
@ -170,7 +170,7 @@ public class MediaPresentationDescriptionParser extends DefaultHandler
|
|||
|
||||
// AdaptationSet parsing.
|
||||
|
||||
protected AdaptationSet parseAdaptationSet(XmlPullParser xpp, Uri baseUrl, long periodStartMs,
|
||||
protected AdaptationSet parseAdaptationSet(XmlPullParser xpp, String baseUrl, long periodStartMs,
|
||||
long periodDurationMs, SegmentBase segmentBase) throws XmlPullParserException, IOException {
|
||||
|
||||
String mimeType = xpp.getAttributeValue(null, "mimeType");
|
||||
|
|
@ -287,9 +287,9 @@ public class MediaPresentationDescriptionParser extends DefaultHandler
|
|||
|
||||
// Representation parsing.
|
||||
|
||||
protected Representation parseRepresentation(XmlPullParser xpp, Uri baseUrl, long periodStartMs,
|
||||
long periodDurationMs, String mimeType, String language, SegmentBase segmentBase)
|
||||
throws XmlPullParserException, IOException {
|
||||
protected Representation parseRepresentation(XmlPullParser xpp, String baseUrl,
|
||||
long periodStartMs, long periodDurationMs, String mimeType, String language,
|
||||
SegmentBase segmentBase) throws XmlPullParserException, IOException {
|
||||
String id = xpp.getAttributeValue(null, "id");
|
||||
int bandwidth = parseInt(xpp, "bandwidth");
|
||||
int audioSamplingRate = parseInt(xpp, "audioSamplingRate");
|
||||
|
|
@ -335,7 +335,7 @@ public class MediaPresentationDescriptionParser extends DefaultHandler
|
|||
|
||||
// SegmentBase, SegmentList and SegmentTemplate parsing.
|
||||
|
||||
protected SingleSegmentBase parseSegmentBase(XmlPullParser xpp, Uri baseUrl,
|
||||
protected SingleSegmentBase parseSegmentBase(XmlPullParser xpp, String baseUrl,
|
||||
SingleSegmentBase parent) throws XmlPullParserException, IOException {
|
||||
|
||||
long timescale = parseLong(xpp, "timescale", parent != null ? parent.timescale : 1);
|
||||
|
|
@ -364,12 +364,12 @@ public class MediaPresentationDescriptionParser extends DefaultHandler
|
|||
}
|
||||
|
||||
protected SingleSegmentBase buildSingleSegmentBase(RangedUri initialization, long timescale,
|
||||
long presentationTimeOffset, Uri baseUrl, long indexStart, long indexLength) {
|
||||
long presentationTimeOffset, String baseUrl, long indexStart, long indexLength) {
|
||||
return new SingleSegmentBase(initialization, timescale, presentationTimeOffset, baseUrl,
|
||||
indexStart, indexLength);
|
||||
}
|
||||
|
||||
protected SegmentList parseSegmentList(XmlPullParser xpp, Uri baseUrl, SegmentList parent,
|
||||
protected SegmentList parseSegmentList(XmlPullParser xpp, String baseUrl, SegmentList parent,
|
||||
long periodDurationMs) throws XmlPullParserException, IOException {
|
||||
|
||||
long timescale = parseLong(xpp, "timescale", parent != null ? parent.timescale : 1);
|
||||
|
|
@ -413,7 +413,7 @@ public class MediaPresentationDescriptionParser extends DefaultHandler
|
|||
startNumber, duration, timeline, segments);
|
||||
}
|
||||
|
||||
protected SegmentTemplate parseSegmentTemplate(XmlPullParser xpp, Uri baseUrl,
|
||||
protected SegmentTemplate parseSegmentTemplate(XmlPullParser xpp, String baseUrl,
|
||||
SegmentTemplate parent, long periodDurationMs) throws XmlPullParserException, IOException {
|
||||
|
||||
long timescale = parseLong(xpp, "timescale", parent != null ? parent.timescale : 1);
|
||||
|
|
@ -450,7 +450,7 @@ public class MediaPresentationDescriptionParser extends DefaultHandler
|
|||
protected SegmentTemplate buildSegmentTemplate(RangedUri initialization, long timescale,
|
||||
long presentationTimeOffset, long periodDurationMs, int startNumber, long duration,
|
||||
List<SegmentTimelineElement> timeline, UrlTemplate initializationTemplate,
|
||||
UrlTemplate mediaTemplate, Uri baseUrl) {
|
||||
UrlTemplate mediaTemplate, String baseUrl) {
|
||||
return new SegmentTemplate(initialization, timescale, presentationTimeOffset, periodDurationMs,
|
||||
startNumber, duration, timeline, initializationTemplate, mediaTemplate, baseUrl);
|
||||
}
|
||||
|
|
@ -487,15 +487,15 @@ public class MediaPresentationDescriptionParser extends DefaultHandler
|
|||
return defaultValue;
|
||||
}
|
||||
|
||||
protected RangedUri parseInitialization(XmlPullParser xpp, Uri baseUrl) {
|
||||
protected RangedUri parseInitialization(XmlPullParser xpp, String baseUrl) {
|
||||
return parseRangedUrl(xpp, baseUrl, "sourceURL", "range");
|
||||
}
|
||||
|
||||
protected RangedUri parseSegmentUrl(XmlPullParser xpp, Uri baseUrl) {
|
||||
protected RangedUri parseSegmentUrl(XmlPullParser xpp, String baseUrl) {
|
||||
return parseRangedUrl(xpp, baseUrl, "media", "mediaRange");
|
||||
}
|
||||
|
||||
protected RangedUri parseRangedUrl(XmlPullParser xpp, Uri baseUrl, String urlAttribute,
|
||||
protected RangedUri parseRangedUrl(XmlPullParser xpp, String baseUrl, String urlAttribute,
|
||||
String rangeAttribute) {
|
||||
String urlText = xpp.getAttributeValue(null, urlAttribute);
|
||||
long rangeStart = 0;
|
||||
|
|
@ -509,7 +509,7 @@ public class MediaPresentationDescriptionParser extends DefaultHandler
|
|||
return buildRangedUri(baseUrl, urlText, rangeStart, rangeLength);
|
||||
}
|
||||
|
||||
protected RangedUri buildRangedUri(Uri baseUrl, String urlText, long rangeStart,
|
||||
protected RangedUri buildRangedUri(String baseUrl, String urlText, long rangeStart,
|
||||
long rangeLength) {
|
||||
return new RangedUri(baseUrl, urlText, rangeStart, rangeLength);
|
||||
}
|
||||
|
|
@ -548,15 +548,10 @@ public class MediaPresentationDescriptionParser extends DefaultHandler
|
|||
}
|
||||
}
|
||||
|
||||
protected static Uri parseBaseUrl(XmlPullParser xpp, Uri parentBaseUrl)
|
||||
protected static String parseBaseUrl(XmlPullParser xpp, String parentBaseUrl)
|
||||
throws XmlPullParserException, IOException {
|
||||
xpp.next();
|
||||
String newBaseUrlText = xpp.getText();
|
||||
Uri newBaseUri = Uri.parse(newBaseUrlText);
|
||||
if (!newBaseUri.isAbsolute()) {
|
||||
newBaseUri = Uri.withAppendedPath(parentBaseUrl, newBaseUrlText);
|
||||
}
|
||||
return newBaseUri;
|
||||
return UriUtil.resolve(parentBaseUrl, xpp.getText());
|
||||
}
|
||||
|
||||
protected static int parseInt(XmlPullParser xpp, String name) {
|
||||
|
|
|
|||
|
|
@ -16,7 +16,7 @@
|
|||
package com.google.android.exoplayer.dash.mpd;
|
||||
|
||||
import com.google.android.exoplayer.util.Assertions;
|
||||
import com.google.android.exoplayer.util.Util;
|
||||
import com.google.android.exoplayer.util.UriUtil;
|
||||
|
||||
import android.net.Uri;
|
||||
|
||||
|
|
@ -35,31 +35,28 @@ public final class RangedUri {
|
|||
*/
|
||||
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
|
||||
// The URI is stored internally in two parts: reference URI and a base URI to use when
|
||||
// resolving it. 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 object being passed as the base URI to many
|
||||
// instances of this class.
|
||||
private final Uri baseUri;
|
||||
private final String stringUri;
|
||||
private final String baseUri;
|
||||
private final String referenceUri;
|
||||
|
||||
private int hashCode;
|
||||
|
||||
/**
|
||||
* Constructs an ranged uri.
|
||||
* <p>
|
||||
* See {@link Util#getMergedUri(Uri, String)} for a description of how {@code baseUri} and
|
||||
* {@code stringUri} are merged.
|
||||
*
|
||||
* @param baseUri A uri that can form the base of the uri defined by the instance.
|
||||
* @param stringUri A relative or absolute uri in string form.
|
||||
* @param referenceUri A reference uri that should be resolved with respect to {@code baseUri}.
|
||||
* @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);
|
||||
public RangedUri(String baseUri, String referenceUri, long start, long length) {
|
||||
Assertions.checkArgument(baseUri != null || referenceUri != null);
|
||||
this.baseUri = baseUri;
|
||||
this.stringUri = stringUri;
|
||||
this.referenceUri = referenceUri;
|
||||
this.start = start;
|
||||
this.length = length;
|
||||
}
|
||||
|
|
@ -70,7 +67,16 @@ public final class RangedUri {
|
|||
* @return The {@link Uri} represented by the instance.
|
||||
*/
|
||||
public Uri getUri() {
|
||||
return Util.getMergedUri(baseUri, stringUri);
|
||||
return UriUtil.resolveToUri(baseUri, referenceUri);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the uri represented by the instance as a string.
|
||||
*
|
||||
* @return The uri represented by the instance.
|
||||
*/
|
||||
public String getUriString() {
|
||||
return UriUtil.resolve(baseUri, referenceUri);
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -85,13 +91,13 @@ public final class RangedUri {
|
|||
* @return The merged {@link RangedUri} if the merge was successful. Null otherwise.
|
||||
*/
|
||||
public RangedUri attemptMerge(RangedUri other) {
|
||||
if (other == null || !getUri().equals(other.getUri())) {
|
||||
if (other == null || !getUriString().equals(other.getUriString())) {
|
||||
return null;
|
||||
} else if (length != -1 && start + length == other.start) {
|
||||
return new RangedUri(baseUri, stringUri, start,
|
||||
return new RangedUri(baseUri, referenceUri, start,
|
||||
other.length == -1 ? -1 : length + other.length);
|
||||
} else if (other.length != -1 && other.start + other.length == start) {
|
||||
return new RangedUri(baseUri, stringUri, other.start,
|
||||
return new RangedUri(baseUri, referenceUri, other.start,
|
||||
length == -1 ? -1 : other.length + length);
|
||||
} else {
|
||||
return null;
|
||||
|
|
@ -104,7 +110,7 @@ public final class RangedUri {
|
|||
int result = 17;
|
||||
result = 31 * result + (int) start;
|
||||
result = 31 * result + (int) length;
|
||||
result = 31 * result + getUri().hashCode();
|
||||
result = 31 * result + getUriString().hashCode();
|
||||
hashCode = result;
|
||||
}
|
||||
return hashCode;
|
||||
|
|
@ -121,7 +127,7 @@ public final class RangedUri {
|
|||
RangedUri other = (RangedUri) obj;
|
||||
return this.start == other.start
|
||||
&& this.length == other.length
|
||||
&& getUri().equals(other.getUri());
|
||||
&& getUriString().equals(other.getUriString());
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -147,7 +147,7 @@ public abstract class Representation {
|
|||
public static class SingleSegmentRepresentation extends Representation {
|
||||
|
||||
/**
|
||||
* The {@link Uri} of the single segment.
|
||||
* The uri of the single segment.
|
||||
*/
|
||||
public final Uri uri;
|
||||
|
||||
|
|
@ -174,7 +174,7 @@ public abstract class Representation {
|
|||
* @param contentLength The content length, or -1 if unknown.
|
||||
*/
|
||||
public static SingleSegmentRepresentation newInstance(long periodStartMs, long periodDurationMs,
|
||||
String contentId, long revisionId, Format format, Uri uri, long initializationStart,
|
||||
String contentId, long revisionId, Format format, String uri, long initializationStart,
|
||||
long initializationEnd, long indexStart, long indexEnd, long contentLength) {
|
||||
RangedUri rangedUri = new RangedUri(uri, null, initializationStart,
|
||||
initializationEnd - initializationStart + 1);
|
||||
|
|
@ -197,13 +197,13 @@ public abstract class Representation {
|
|||
public SingleSegmentRepresentation(long periodStartMs, long periodDurationMs, String contentId,
|
||||
long revisionId, Format format, SingleSegmentBase segmentBase, long contentLength) {
|
||||
super(periodStartMs, periodDurationMs, contentId, revisionId, format, segmentBase);
|
||||
this.uri = segmentBase.uri;
|
||||
this.uri = Uri.parse(segmentBase.uri);
|
||||
this.indexUri = segmentBase.getIndex();
|
||||
this.contentLength = contentLength;
|
||||
// If we have an index uri then the index is defined externally, and we shouldn't return one
|
||||
// directly. If we don't, then we can't do better than an index defining a single segment.
|
||||
segmentIndex = indexUri != null ? null : new DashSingleSegmentIndex(periodStartMs * 1000,
|
||||
periodDurationMs * 1000, new RangedUri(uri, null, 0, -1));
|
||||
periodDurationMs * 1000, new RangedUri(segmentBase.uri, null, 0, -1));
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
|
|||
|
|
@ -19,8 +19,6 @@ import com.google.android.exoplayer.C;
|
|||
import com.google.android.exoplayer.dash.DashSegmentIndex;
|
||||
import com.google.android.exoplayer.util.Util;
|
||||
|
||||
import android.net.Uri;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
|
|
@ -73,7 +71,7 @@ public abstract class SegmentBase {
|
|||
/**
|
||||
* The uri of the segment.
|
||||
*/
|
||||
public final Uri uri;
|
||||
public final String uri;
|
||||
|
||||
/* package */ final long indexStart;
|
||||
/* package */ final long indexLength;
|
||||
|
|
@ -89,7 +87,7 @@ public abstract class SegmentBase {
|
|||
* @param indexLength The length of the index data in bytes.
|
||||
*/
|
||||
public SingleSegmentBase(RangedUri initialization, long timescale, long presentationTimeOffset,
|
||||
Uri uri, long indexStart, long indexLength) {
|
||||
String uri, long indexStart, long indexLength) {
|
||||
super(initialization, timescale, presentationTimeOffset);
|
||||
this.uri = uri;
|
||||
this.indexStart = indexStart;
|
||||
|
|
@ -99,7 +97,7 @@ public abstract class SegmentBase {
|
|||
/**
|
||||
* @param uri The uri of the segment.
|
||||
*/
|
||||
public SingleSegmentBase(Uri uri) {
|
||||
public SingleSegmentBase(String uri) {
|
||||
this(null, 1, 0, uri, 0, -1);
|
||||
}
|
||||
|
||||
|
|
@ -289,7 +287,7 @@ public abstract class SegmentBase {
|
|||
/* package */ final UrlTemplate initializationTemplate;
|
||||
/* package */ final UrlTemplate mediaTemplate;
|
||||
|
||||
private final Uri baseUrl;
|
||||
private final String baseUrl;
|
||||
|
||||
/**
|
||||
* @param initialization A {@link RangedUri} corresponding to initialization data, if such data
|
||||
|
|
@ -315,7 +313,7 @@ public abstract class SegmentBase {
|
|||
public SegmentTemplate(RangedUri initialization, long timescale, long presentationTimeOffset,
|
||||
long periodDurationMs, int startNumber, long duration,
|
||||
List<SegmentTimelineElement> segmentTimeline, UrlTemplate initializationTemplate,
|
||||
UrlTemplate mediaTemplate, Uri baseUrl) {
|
||||
UrlTemplate mediaTemplate, String baseUrl) {
|
||||
super(initialization, timescale, presentationTimeOffset, periodDurationMs, startNumber,
|
||||
duration, segmentTimeline);
|
||||
this.initializationTemplate = initializationTemplate;
|
||||
|
|
|
|||
|
|
@ -32,6 +32,7 @@ import java.io.InputStreamReader;
|
|||
import java.text.ParseException;
|
||||
import java.text.SimpleDateFormat;
|
||||
import java.util.Locale;
|
||||
import java.util.TimeZone;
|
||||
import java.util.concurrent.CancellationException;
|
||||
|
||||
/**
|
||||
|
|
@ -173,6 +174,7 @@ public class UtcTimingElementResolver implements Loader.Callback {
|
|||
try {
|
||||
// TODO: It may be necessary to handle timestamp offsets from UTC.
|
||||
SimpleDateFormat format = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss'Z'", Locale.US);
|
||||
format.setTimeZone(TimeZone.getTimeZone("UTC"));
|
||||
return format.parse(firstLine).getTime();
|
||||
} catch (ParseException e) {
|
||||
throw new ParserException(e);
|
||||
|
|
|
|||
|
|
@ -27,6 +27,7 @@ import com.google.android.exoplayer.upstream.DataSource;
|
|||
import com.google.android.exoplayer.upstream.DataSpec;
|
||||
import com.google.android.exoplayer.upstream.HttpDataSource.InvalidResponseCodeException;
|
||||
import com.google.android.exoplayer.util.Assertions;
|
||||
import com.google.android.exoplayer.util.UriUtil;
|
||||
import com.google.android.exoplayer.util.Util;
|
||||
|
||||
import android.net.Uri;
|
||||
|
|
@ -106,6 +107,11 @@ public class HlsChunkSource {
|
|||
*/
|
||||
public static final long DEFAULT_MAX_BUFFER_TO_SWITCH_DOWN_MS = 20000;
|
||||
|
||||
/**
|
||||
* The default time for which a media playlist should be blacklisted.
|
||||
*/
|
||||
public static final long DEFAULT_PLAYLIST_BLACKLIST_MS = 60000;
|
||||
|
||||
private static final String TAG = "HlsChunkSource";
|
||||
private static final String AAC_FILE_EXTENSION = ".aac";
|
||||
private static final float BANDWIDTH_FRACTION = 0.8f;
|
||||
|
|
@ -116,7 +122,7 @@ public class HlsChunkSource {
|
|||
private final Variant[] enabledVariants;
|
||||
private final BandwidthMeter bandwidthMeter;
|
||||
private final int adaptiveMode;
|
||||
private final Uri baseUri;
|
||||
private final String baseUri;
|
||||
private final int maxWidth;
|
||||
private final int maxHeight;
|
||||
private final int targetBufferSize;
|
||||
|
|
@ -126,7 +132,7 @@ public class HlsChunkSource {
|
|||
|
||||
/* package */ byte[] scratchSpace;
|
||||
/* package */ final HlsMediaPlaylist[] mediaPlaylists;
|
||||
/* package */ final boolean[] mediaPlaylistBlacklistFlags;
|
||||
/* package */ final long[] mediaPlaylistBlacklistTimesMs;
|
||||
/* package */ final long[] lastMediaPlaylistLoadTimesMs;
|
||||
/* package */ boolean live;
|
||||
/* package */ long durationUs;
|
||||
|
|
@ -181,14 +187,14 @@ public class HlsChunkSource {
|
|||
if (playlist.type == HlsPlaylist.TYPE_MEDIA) {
|
||||
enabledVariants = new Variant[] {new Variant(0, playlistUrl, 0, null, -1, -1)};
|
||||
mediaPlaylists = new HlsMediaPlaylist[1];
|
||||
mediaPlaylistBlacklistFlags = new boolean[1];
|
||||
mediaPlaylistBlacklistTimesMs = new long[1];
|
||||
lastMediaPlaylistLoadTimesMs = new long[1];
|
||||
setMediaPlaylist(0, (HlsMediaPlaylist) playlist);
|
||||
} else {
|
||||
Assertions.checkState(playlist.type == HlsPlaylist.TYPE_MASTER);
|
||||
enabledVariants = filterVariants((HlsMasterPlaylist) playlist, variantIndices);
|
||||
mediaPlaylists = new HlsMediaPlaylist[enabledVariants.length];
|
||||
mediaPlaylistBlacklistFlags = new boolean[enabledVariants.length];
|
||||
mediaPlaylistBlacklistTimesMs = new long[enabledVariants.length];
|
||||
lastMediaPlaylistLoadTimesMs = new long[enabledVariants.length];
|
||||
}
|
||||
|
||||
|
|
@ -296,11 +302,11 @@ public class HlsChunkSource {
|
|||
}
|
||||
|
||||
HlsMediaPlaylist.Segment segment = mediaPlaylist.segments.get(chunkIndex);
|
||||
Uri chunkUri = Util.getMergedUri(mediaPlaylist.baseUri, segment.url);
|
||||
Uri chunkUri = UriUtil.resolveToUri(mediaPlaylist.baseUri, segment.url);
|
||||
|
||||
// Check if encryption is specified.
|
||||
if (HlsMediaPlaylist.ENCRYPTION_METHOD_AES_128.equals(segment.encryptionMethod)) {
|
||||
Uri keyUri = Util.getMergedUri(mediaPlaylist.baseUri, segment.encryptionKeyUri);
|
||||
Uri keyUri = UriUtil.resolveToUri(mediaPlaylist.baseUri, segment.encryptionKeyUri);
|
||||
if (!keyUri.equals(encryptionKeyUri)) {
|
||||
// Encryption is specified and the key has changed.
|
||||
HlsChunk toReturn = newEncryptionKeyChunk(keyUri, segment.encryptionIV);
|
||||
|
|
@ -361,7 +367,7 @@ public class HlsChunkSource {
|
|||
int responseCode = responseCodeException.responseCode;
|
||||
if (responseCode == 404 || responseCode == 410) {
|
||||
MediaPlaylistChunk playlistChunk = (MediaPlaylistChunk) chunk;
|
||||
mediaPlaylistBlacklistFlags[playlistChunk.variantIndex] = true;
|
||||
mediaPlaylistBlacklistTimesMs[playlistChunk.variantIndex] = SystemClock.elapsedRealtime();
|
||||
if (!allPlaylistsBlacklisted()) {
|
||||
// We've handled the 404/410 by blacklisting the playlist.
|
||||
Log.w(TAG, "Blacklisted playlist (" + responseCode + "): "
|
||||
|
|
@ -371,7 +377,7 @@ public class HlsChunkSource {
|
|||
// This was the last non-blacklisted playlist. Don't blacklist it.
|
||||
Log.w(TAG, "Final playlist not blacklisted (" + responseCode + "): "
|
||||
+ playlistChunk.dataSpec.uri);
|
||||
mediaPlaylistBlacklistFlags[playlistChunk.variantIndex] = false;
|
||||
mediaPlaylistBlacklistTimesMs[playlistChunk.variantIndex] = 0;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
|
@ -380,6 +386,7 @@ public class HlsChunkSource {
|
|||
}
|
||||
|
||||
private int getNextVariantIndex(TsChunk previousTsChunk, long playbackPositionUs) {
|
||||
clearStaleBlacklistedPlaylists();
|
||||
int idealVariantIndex = getVariantIndexForBandwdith(
|
||||
(int) (bandwidthMeter.getBitrateEstimate() * BANDWIDTH_FRACTION));
|
||||
if (idealVariantIndex == variantIndex) {
|
||||
|
|
@ -392,7 +399,7 @@ public class HlsChunkSource {
|
|||
: adaptiveMode == ADAPTIVE_MODE_SPLICE ? previousTsChunk.startTimeUs
|
||||
: previousTsChunk.endTimeUs;
|
||||
long bufferedUs = bufferedPositionUs - playbackPositionUs;
|
||||
if (mediaPlaylistBlacklistFlags[variantIndex]
|
||||
if (mediaPlaylistBlacklistTimesMs[variantIndex] != 0
|
||||
|| (idealVariantIndex > variantIndex && bufferedUs < maxBufferDurationToSwitchDownUs)
|
||||
|| (idealVariantIndex < variantIndex && bufferedUs > minBufferDurationToSwitchUpUs)) {
|
||||
// Switch variant.
|
||||
|
|
@ -405,7 +412,7 @@ public class HlsChunkSource {
|
|||
private int getVariantIndexForBandwdith(int bandwidth) {
|
||||
int lowestQualityEnabledVariant = 0;
|
||||
for (int i = 0; i < enabledVariants.length; i++) {
|
||||
if (!mediaPlaylistBlacklistFlags[i]) {
|
||||
if (mediaPlaylistBlacklistTimesMs[i] == 0) {
|
||||
if (enabledVariants[i].bandwidth <= bandwidth) {
|
||||
return i;
|
||||
}
|
||||
|
|
@ -431,14 +438,15 @@ public class HlsChunkSource {
|
|||
}
|
||||
|
||||
private MediaPlaylistChunk newMediaPlaylistChunk(int variantIndex) {
|
||||
Uri mediaPlaylistUri = Util.getMergedUri(baseUri, enabledVariants[variantIndex].url);
|
||||
DataSpec dataSpec = new DataSpec(mediaPlaylistUri, 0, C.LENGTH_UNBOUNDED, null);
|
||||
Uri mediaPlaylistUri = UriUtil.resolveToUri(baseUri, enabledVariants[variantIndex].url);
|
||||
DataSpec dataSpec = new DataSpec(mediaPlaylistUri, 0, C.LENGTH_UNBOUNDED, null,
|
||||
DataSpec.FLAG_ALLOW_GZIP);
|
||||
return new MediaPlaylistChunk(variantIndex, upstreamDataSource, dataSpec,
|
||||
mediaPlaylistUri.toString());
|
||||
}
|
||||
|
||||
private EncryptionKeyChunk newEncryptionKeyChunk(Uri keyUri, String iv) {
|
||||
DataSpec dataSpec = new DataSpec(keyUri, 0, C.LENGTH_UNBOUNDED, null);
|
||||
DataSpec dataSpec = new DataSpec(keyUri, 0, C.LENGTH_UNBOUNDED, null, DataSpec.FLAG_ALLOW_GZIP);
|
||||
return new EncryptionKeyChunk(upstreamDataSource, dataSpec, iv);
|
||||
}
|
||||
|
||||
|
|
@ -533,14 +541,24 @@ public class HlsChunkSource {
|
|||
}
|
||||
|
||||
private boolean allPlaylistsBlacklisted() {
|
||||
for (int i = 0; i < mediaPlaylistBlacklistFlags.length; i++) {
|
||||
if (!mediaPlaylistBlacklistFlags[i]) {
|
||||
for (int i = 0; i < mediaPlaylistBlacklistTimesMs.length; i++) {
|
||||
if (mediaPlaylistBlacklistTimesMs[i] == 0) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
private void clearStaleBlacklistedPlaylists() {
|
||||
long currentTime = SystemClock.elapsedRealtime();
|
||||
for (int i = 0; i < mediaPlaylistBlacklistTimesMs.length; i++) {
|
||||
if (mediaPlaylistBlacklistTimesMs[i] != 0
|
||||
&& currentTime - mediaPlaylistBlacklistTimesMs[i] > DEFAULT_PLAYLIST_BLACKLIST_MS) {
|
||||
mediaPlaylistBlacklistTimesMs[i] = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private class MediaPlaylistChunk extends DataChunk {
|
||||
|
||||
@SuppressWarnings("hiding")
|
||||
|
|
|
|||
|
|
@ -15,8 +15,6 @@
|
|||
*/
|
||||
package com.google.android.exoplayer.hls;
|
||||
|
||||
import android.net.Uri;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
|
|
@ -26,7 +24,7 @@ public final class HlsMasterPlaylist extends HlsPlaylist {
|
|||
|
||||
public final List<Variant> variants;
|
||||
|
||||
public HlsMasterPlaylist(Uri baseUri, List<Variant> variants) {
|
||||
public HlsMasterPlaylist(String baseUri, List<Variant> variants) {
|
||||
super(baseUri, HlsPlaylist.TYPE_MASTER);
|
||||
this.variants = variants;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -17,8 +17,6 @@ package com.google.android.exoplayer.hls;
|
|||
|
||||
import com.google.android.exoplayer.C;
|
||||
|
||||
import android.net.Uri;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
|
|
@ -70,7 +68,7 @@ public final class HlsMediaPlaylist extends HlsPlaylist {
|
|||
public final boolean live;
|
||||
public final long durationUs;
|
||||
|
||||
public HlsMediaPlaylist(Uri baseUri, int mediaSequence, int targetDurationSecs, int version,
|
||||
public HlsMediaPlaylist(String baseUri, int mediaSequence, int targetDurationSecs, int version,
|
||||
boolean live, List<Segment> segments) {
|
||||
super(baseUri, HlsPlaylist.TYPE_MEDIA);
|
||||
this.mediaSequence = mediaSequence;
|
||||
|
|
|
|||
|
|
@ -15,7 +15,6 @@
|
|||
*/
|
||||
package com.google.android.exoplayer.hls;
|
||||
|
||||
import android.net.Uri;
|
||||
|
||||
/**
|
||||
* Represents an HLS playlist.
|
||||
|
|
@ -25,10 +24,10 @@ public abstract class HlsPlaylist {
|
|||
public final static int TYPE_MASTER = 0;
|
||||
public final static int TYPE_MEDIA = 1;
|
||||
|
||||
public final Uri baseUri;
|
||||
public final String baseUri;
|
||||
public final int type;
|
||||
|
||||
protected HlsPlaylist(Uri baseUri, int type) {
|
||||
protected HlsPlaylist(String baseUri, int type) {
|
||||
this.baseUri = baseUri;
|
||||
this.type = type;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -19,9 +19,6 @@ import com.google.android.exoplayer.C;
|
|||
import com.google.android.exoplayer.ParserException;
|
||||
import com.google.android.exoplayer.hls.HlsMediaPlaylist.Segment;
|
||||
import com.google.android.exoplayer.upstream.NetworkLoadable;
|
||||
import com.google.android.exoplayer.util.Util;
|
||||
|
||||
import android.net.Uri;
|
||||
|
||||
import java.io.BufferedReader;
|
||||
import java.io.IOException;
|
||||
|
|
@ -86,7 +83,6 @@ public final class HlsPlaylistParser implements NetworkLoadable.Parser<HlsPlayli
|
|||
@Override
|
||||
public HlsPlaylist parse(String connectionUrl, InputStream inputStream)
|
||||
throws IOException, ParserException {
|
||||
Uri baseUri = Util.parseBaseUri(connectionUrl);
|
||||
BufferedReader reader = new BufferedReader(new InputStreamReader(inputStream));
|
||||
Queue<String> extraLines = new LinkedList<String>();
|
||||
String line;
|
||||
|
|
@ -97,7 +93,7 @@ public final class HlsPlaylistParser implements NetworkLoadable.Parser<HlsPlayli
|
|||
// Do nothing.
|
||||
} else if (line.startsWith(STREAM_INF_TAG)) {
|
||||
extraLines.add(line);
|
||||
return parseMasterPlaylist(new LineIterator(extraLines, reader), baseUri);
|
||||
return parseMasterPlaylist(new LineIterator(extraLines, reader), connectionUrl);
|
||||
} else if (line.startsWith(TARGET_DURATION_TAG)
|
||||
|| line.startsWith(MEDIA_SEQUENCE_TAG)
|
||||
|| line.startsWith(MEDIA_DURATION_TAG)
|
||||
|
|
@ -106,7 +102,7 @@ public final class HlsPlaylistParser implements NetworkLoadable.Parser<HlsPlayli
|
|||
|| line.equals(DISCONTINUITY_TAG)
|
||||
|| line.equals(ENDLIST_TAG)) {
|
||||
extraLines.add(line);
|
||||
return parseMediaPlaylist(new LineIterator(extraLines, reader), baseUri);
|
||||
return parseMediaPlaylist(new LineIterator(extraLines, reader), connectionUrl);
|
||||
} else if (line.startsWith(VERSION_TAG)) {
|
||||
extraLines.add(line);
|
||||
} else if (!line.startsWith("#")) {
|
||||
|
|
@ -119,7 +115,7 @@ public final class HlsPlaylistParser implements NetworkLoadable.Parser<HlsPlayli
|
|||
throw new ParserException("Failed to parse the playlist, could not identify any tags.");
|
||||
}
|
||||
|
||||
private static HlsMasterPlaylist parseMasterPlaylist(LineIterator iterator, Uri baseUri)
|
||||
private static HlsMasterPlaylist parseMasterPlaylist(LineIterator iterator, String baseUri)
|
||||
throws IOException {
|
||||
List<Variant> variants = new ArrayList<Variant>();
|
||||
int bandwidth = 0;
|
||||
|
|
@ -160,7 +156,7 @@ public final class HlsPlaylistParser implements NetworkLoadable.Parser<HlsPlayli
|
|||
return new HlsMasterPlaylist(baseUri, Collections.unmodifiableList(variants));
|
||||
}
|
||||
|
||||
private static HlsMediaPlaylist parseMediaPlaylist(LineIterator iterator, Uri baseUri)
|
||||
private static HlsMediaPlaylist parseMediaPlaylist(LineIterator iterator, String baseUri)
|
||||
throws IOException {
|
||||
int mediaSequence = 0;
|
||||
int targetDurationSecs = 0;
|
||||
|
|
|
|||
|
|
@ -0,0 +1,38 @@
|
|||
/*
|
||||
* 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.metadata;
|
||||
|
||||
/**
|
||||
* A metadata that contains parsed ID3 GEOB (General Encapsulated Object) frame data associated
|
||||
* with time indices.
|
||||
*/
|
||||
public class GeobMetadata {
|
||||
|
||||
public static final String TYPE = "GEOB";
|
||||
|
||||
public final String mimeType;
|
||||
public final String filename;
|
||||
public final String description;
|
||||
public final byte[] data;
|
||||
|
||||
public GeobMetadata(String mimeType, String filename, String description, byte[] data) {
|
||||
this.mimeType = mimeType;
|
||||
this.filename = filename;
|
||||
this.description = description;
|
||||
this.data = data;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -29,6 +29,11 @@ import java.util.Map;
|
|||
*/
|
||||
public class Id3Parser implements MetadataParser<Map<String, Object>> {
|
||||
|
||||
private static final int ID3_TEXT_ENCODING_ISO_8859_1 = 0;
|
||||
private static final int ID3_TEXT_ENCODING_UTF_16 = 1;
|
||||
private static final int ID3_TEXT_ENCODING_UTF_16BE = 2;
|
||||
private static final int ID3_TEXT_ENCODING_UTF_8 = 3;
|
||||
|
||||
@Override
|
||||
public boolean canParse(String mimeType) {
|
||||
return mimeType.equals(MimeTypes.APPLICATION_ID3);
|
||||
|
|
@ -60,13 +65,48 @@ public class Id3Parser implements MetadataParser<Map<String, Object>> {
|
|||
byte[] frame = new byte[frameSize - 1];
|
||||
id3Data.readBytes(frame, 0, frameSize - 1);
|
||||
|
||||
int firstZeroIndex = indexOf(frame, 0, (byte) 0);
|
||||
int firstZeroIndex = indexOfEOS(frame, 0, encoding);
|
||||
String description = new String(frame, 0, firstZeroIndex, charset);
|
||||
int valueStartIndex = indexOfNot(frame, firstZeroIndex, (byte) 0);
|
||||
int valueEndIndex = indexOf(frame, valueStartIndex, (byte) 0);
|
||||
int valueStartIndex = firstZeroIndex + delimiterLength(encoding);
|
||||
int valueEndIndex = indexOfEOS(frame, valueStartIndex, encoding);
|
||||
String value = new String(frame, valueStartIndex, valueEndIndex - valueStartIndex,
|
||||
charset);
|
||||
metadata.put(TxxxMetadata.TYPE, new TxxxMetadata(description, value));
|
||||
} else if (frameId0 == 'P' && frameId1 == 'R' && frameId2 == 'I' && frameId3 == 'V') {
|
||||
// Check frame ID == PRIV
|
||||
byte[] frame = new byte[frameSize];
|
||||
id3Data.readBytes(frame, 0, frameSize);
|
||||
|
||||
int firstZeroIndex = indexOf(frame, 0, (byte) 0);
|
||||
String owner = new String(frame, 0, firstZeroIndex, "ISO-8859-1");
|
||||
byte[] privateData = new byte[frameSize - firstZeroIndex - 1];
|
||||
System.arraycopy(frame, firstZeroIndex + 1, privateData, 0, frameSize - firstZeroIndex - 1);
|
||||
metadata.put(PrivMetadata.TYPE, new PrivMetadata(owner, privateData));
|
||||
} else if (frameId0 == 'G' && frameId1 == 'E' && frameId2 == 'O' && frameId3 == 'B') {
|
||||
// Check frame ID == GEOB
|
||||
int encoding = id3Data.readUnsignedByte();
|
||||
String charset = getCharsetName(encoding);
|
||||
byte[] frame = new byte[frameSize - 1];
|
||||
id3Data.readBytes(frame, 0, frameSize - 1);
|
||||
|
||||
int firstZeroIndex = indexOf(frame, 0, (byte) 0);
|
||||
String mimeType = new String(frame, 0, firstZeroIndex, "ISO-8859-1");
|
||||
int filenameStartIndex = firstZeroIndex + 1;
|
||||
int filenameEndIndex = indexOfEOS(frame, filenameStartIndex, encoding);
|
||||
String filename = new String(frame, filenameStartIndex,
|
||||
filenameEndIndex - filenameStartIndex, charset);
|
||||
int descriptionStartIndex = filenameEndIndex + delimiterLength(encoding);
|
||||
int descriptionEndIndex = indexOfEOS(frame, descriptionStartIndex, encoding);
|
||||
String description = new String(frame, descriptionStartIndex,
|
||||
descriptionEndIndex - descriptionStartIndex, charset);
|
||||
|
||||
int objectDataSize = frameSize - 1 /* encoding byte */ - descriptionEndIndex
|
||||
- delimiterLength(encoding);
|
||||
byte[] objectData = new byte[objectDataSize];
|
||||
System.arraycopy(frame, descriptionEndIndex + delimiterLength(encoding), objectData, 0,
|
||||
objectDataSize);
|
||||
metadata.put(GeobMetadata.TYPE, new GeobMetadata(mimeType, filename,
|
||||
description, objectData));
|
||||
} else {
|
||||
String type = String.format("%c%c%c%c", frameId0, frameId1, frameId2, frameId3);
|
||||
byte[] frame = new byte[frameSize];
|
||||
|
|
@ -89,15 +129,30 @@ public class Id3Parser implements MetadataParser<Map<String, Object>> {
|
|||
return data.length;
|
||||
}
|
||||
|
||||
private static int indexOfNot(byte[] data, int fromIndex, byte key) {
|
||||
for (int i = fromIndex; i < data.length; i++) {
|
||||
if (data[i] != key) {
|
||||
return i;
|
||||
}
|
||||
private static int indexOfEOS(byte[] data, int fromIndex, int encodingByte) {
|
||||
int terminationPos = indexOf(data, fromIndex, (byte) 0);
|
||||
|
||||
// For single byte encoding charsets, we are done
|
||||
if (encodingByte == ID3_TEXT_ENCODING_ISO_8859_1 || encodingByte == ID3_TEXT_ENCODING_UTF_8) {
|
||||
return terminationPos;
|
||||
}
|
||||
|
||||
// Otherwise, look for a two zero bytes
|
||||
while (terminationPos < data.length - 1) {
|
||||
if (data[terminationPos + 1] == (byte) 0) {
|
||||
return terminationPos;
|
||||
}
|
||||
terminationPos = indexOf(data, terminationPos + 1, (byte) 0);
|
||||
}
|
||||
|
||||
return data.length;
|
||||
}
|
||||
|
||||
private static int delimiterLength(int encodingByte) {
|
||||
return (encodingByte == ID3_TEXT_ENCODING_ISO_8859_1
|
||||
|| encodingByte == ID3_TEXT_ENCODING_UTF_8) ? 1 : 2;
|
||||
}
|
||||
|
||||
/**
|
||||
* Parses an ID3 header.
|
||||
*
|
||||
|
|
@ -142,13 +197,13 @@ public class Id3Parser implements MetadataParser<Map<String, Object>> {
|
|||
*/
|
||||
private static String getCharsetName(int encodingByte) {
|
||||
switch (encodingByte) {
|
||||
case 0:
|
||||
case ID3_TEXT_ENCODING_ISO_8859_1:
|
||||
return "ISO-8859-1";
|
||||
case 1:
|
||||
case ID3_TEXT_ENCODING_UTF_16:
|
||||
return "UTF-16";
|
||||
case 2:
|
||||
case ID3_TEXT_ENCODING_UTF_16BE:
|
||||
return "UTF-16BE";
|
||||
case 3:
|
||||
case ID3_TEXT_ENCODING_UTF_8:
|
||||
return "UTF-8";
|
||||
default:
|
||||
return "ISO-8859-1";
|
||||
|
|
|
|||
|
|
@ -0,0 +1,34 @@
|
|||
/*
|
||||
* 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.metadata;
|
||||
|
||||
/**
|
||||
* A metadata that contains parsed ID3 PRIV (Private) frame data associated
|
||||
* with time indices.
|
||||
*/
|
||||
public class PrivMetadata {
|
||||
|
||||
public static final String TYPE = "PRIV";
|
||||
|
||||
public final String owner;
|
||||
public final byte[] privateData;
|
||||
|
||||
public PrivMetadata(String owner, byte[] privateData) {
|
||||
this.owner = owner;
|
||||
this.privateData = privateData;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -24,6 +24,7 @@ import java.util.List;
|
|||
|
||||
public abstract class Atom {
|
||||
|
||||
public static final int TYPE_ftyp = getAtomTypeInteger("ftyp");
|
||||
public static final int TYPE_avc1 = getAtomTypeInteger("avc1");
|
||||
public static final int TYPE_avc3 = getAtomTypeInteger("avc3");
|
||||
public static final int TYPE_esds = getAtomTypeInteger("esds");
|
||||
|
|
|
|||
|
|
@ -17,6 +17,7 @@ package com.google.android.exoplayer.smoothstreaming;
|
|||
|
||||
import com.google.android.exoplayer.C;
|
||||
import com.google.android.exoplayer.util.Assertions;
|
||||
import com.google.android.exoplayer.util.UriUtil;
|
||||
import com.google.android.exoplayer.util.Util;
|
||||
|
||||
import android.net.Uri;
|
||||
|
|
@ -197,14 +198,14 @@ public class SmoothStreamingManifest {
|
|||
public final TrackElement[] tracks;
|
||||
public final int chunkCount;
|
||||
|
||||
private final Uri baseUri;
|
||||
private final String baseUri;
|
||||
private final String chunkTemplate;
|
||||
|
||||
private final List<Long> chunkStartTimes;
|
||||
private final long[] chunkStartTimesUs;
|
||||
private final long lastChunkDurationUs;
|
||||
|
||||
public StreamElement(Uri baseUri, String chunkTemplate, int type, String subType,
|
||||
public StreamElement(String baseUri, String chunkTemplate, int type, String subType,
|
||||
long timescale, String name, int qualityLevels, int maxWidth, int maxHeight,
|
||||
int displayWidth, int displayHeight, String language, TrackElement[] tracks,
|
||||
List<Long> chunkStartTimes, long lastChunkDuration) {
|
||||
|
|
@ -274,7 +275,7 @@ public class SmoothStreamingManifest {
|
|||
String chunkUrl = chunkTemplate
|
||||
.replace(URL_PLACEHOLDER_BITRATE, Integer.toString(tracks[track].bitrate))
|
||||
.replace(URL_PLACEHOLDER_START_TIME, chunkStartTimes.get(chunkIndex).toString());
|
||||
return Util.getMergedUri(baseUri, chunkUrl);
|
||||
return UriUtil.resolveToUri(baseUri, chunkUrl);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -23,9 +23,7 @@ import com.google.android.exoplayer.upstream.NetworkLoadable;
|
|||
import com.google.android.exoplayer.util.Assertions;
|
||||
import com.google.android.exoplayer.util.CodecSpecificDataUtil;
|
||||
import com.google.android.exoplayer.util.MimeTypes;
|
||||
import com.google.android.exoplayer.util.Util;
|
||||
|
||||
import android.net.Uri;
|
||||
import android.util.Base64;
|
||||
import android.util.Pair;
|
||||
|
||||
|
|
@ -65,8 +63,8 @@ public class SmoothStreamingManifestParser implements
|
|||
try {
|
||||
XmlPullParser xmlParser = xmlParserFactory.newPullParser();
|
||||
xmlParser.setInput(inputStream, null);
|
||||
SmoothStreamMediaParser smoothStreamMediaParser = new SmoothStreamMediaParser(null,
|
||||
Util.parseBaseUri(connectionUrl));
|
||||
SmoothStreamMediaParser smoothStreamMediaParser =
|
||||
new SmoothStreamMediaParser(null, connectionUrl);
|
||||
return (SmoothStreamingManifest) smoothStreamMediaParser.parse(xmlParser);
|
||||
} catch (XmlPullParserException e) {
|
||||
throw new ParserException(e);
|
||||
|
|
@ -89,13 +87,13 @@ public class SmoothStreamingManifestParser implements
|
|||
*/
|
||||
private static abstract class ElementParser {
|
||||
|
||||
private final Uri baseUri;
|
||||
private final String baseUri;
|
||||
private final String tag;
|
||||
|
||||
private final ElementParser parent;
|
||||
private final List<Pair<String, Object>> normalizedAttributes;
|
||||
|
||||
public ElementParser(ElementParser parent, Uri baseUri, String tag) {
|
||||
public ElementParser(ElementParser parent, String baseUri, String tag) {
|
||||
this.parent = parent;
|
||||
this.baseUri = baseUri;
|
||||
this.tag = tag;
|
||||
|
|
@ -158,7 +156,7 @@ public class SmoothStreamingManifestParser implements
|
|||
}
|
||||
}
|
||||
|
||||
private ElementParser newChildParser(ElementParser parent, String name, Uri baseUri) {
|
||||
private ElementParser newChildParser(ElementParser parent, String name, String baseUri) {
|
||||
if (TrackElementParser.TAG.equals(name)) {
|
||||
return new TrackElementParser(parent, baseUri);
|
||||
} else if (ProtectionElementParser.TAG.equals(name)) {
|
||||
|
|
@ -342,7 +340,7 @@ public class SmoothStreamingManifestParser implements
|
|||
private ProtectionElement protectionElement;
|
||||
private List<StreamElement> streamElements;
|
||||
|
||||
public SmoothStreamMediaParser(ElementParser parent, Uri baseUri) {
|
||||
public SmoothStreamMediaParser(ElementParser parent, String baseUri) {
|
||||
super(parent, baseUri, TAG);
|
||||
lookAheadCount = -1;
|
||||
protectionElement = null;
|
||||
|
|
@ -392,7 +390,7 @@ public class SmoothStreamingManifestParser implements
|
|||
private UUID uuid;
|
||||
private byte[] initData;
|
||||
|
||||
public ProtectionElementParser(ElementParser parent, Uri baseUri) {
|
||||
public ProtectionElementParser(ElementParser parent, String baseUri) {
|
||||
super(parent, baseUri, TAG);
|
||||
}
|
||||
|
||||
|
|
@ -455,7 +453,7 @@ public class SmoothStreamingManifestParser implements
|
|||
private static final String KEY_FRAGMENT_START_TIME = "t";
|
||||
private static final String KEY_FRAGMENT_REPEAT_COUNT = "r";
|
||||
|
||||
private final Uri baseUri;
|
||||
private final String baseUri;
|
||||
private final List<TrackElement> tracks;
|
||||
|
||||
private int type;
|
||||
|
|
@ -473,7 +471,7 @@ public class SmoothStreamingManifestParser implements
|
|||
|
||||
private long lastChunkDuration;
|
||||
|
||||
public StreamElementParser(ElementParser parent, Uri baseUri) {
|
||||
public StreamElementParser(ElementParser parent, String baseUri) {
|
||||
super(parent, baseUri, TAG);
|
||||
this.baseUri = baseUri;
|
||||
tracks = new LinkedList<TrackElement>();
|
||||
|
|
@ -615,7 +613,7 @@ public class SmoothStreamingManifestParser implements
|
|||
private int nalUnitLengthField;
|
||||
private String content;
|
||||
|
||||
public TrackElementParser(ElementParser parent, Uri baseUri) {
|
||||
public TrackElementParser(ElementParser parent, String baseUri) {
|
||||
super(parent, baseUri, TAG);
|
||||
this.csd = new LinkedList<byte[]>();
|
||||
}
|
||||
|
|
|
|||
|
|
@ -230,7 +230,7 @@ public final class DataSourceStream implements Loadable, NonBlockingInputStream
|
|||
long remainingLength = resolvedLength != C.LENGTH_UNBOUNDED
|
||||
? resolvedLength - loadPosition : C.LENGTH_UNBOUNDED;
|
||||
loadDataSpec = new DataSpec(dataSpec.uri, dataSpec.position + loadPosition,
|
||||
remainingLength, dataSpec.key);
|
||||
remainingLength, dataSpec.key, dataSpec.flags);
|
||||
dataSource.open(loadDataSpec);
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -25,22 +25,32 @@ import android.net.Uri;
|
|||
*/
|
||||
public final class DataSpec {
|
||||
|
||||
/**
|
||||
* Permits an underlying network stack to request that the server use gzip compression.
|
||||
* <p>
|
||||
* Should not typically be set if the data being requested is already compressed (e.g. most audio
|
||||
* and video requests). May be set when requesting other data.
|
||||
* <p>
|
||||
* When a {@link DataSource} is used to request data with this flag set, and if the
|
||||
* {@link DataSource} does make a network request, then the value returned from
|
||||
* {@link DataSource#open(DataSpec)} will typically be {@link C#LENGTH_UNBOUNDED}. The data read
|
||||
* from {@link DataSource#read(byte[], int, int)} will be the decompressed data.
|
||||
*/
|
||||
public static final int FLAG_ALLOW_GZIP = 1;
|
||||
|
||||
/**
|
||||
* Identifies the source from which data should be read.
|
||||
*/
|
||||
public final Uri uri;
|
||||
/**
|
||||
* True if the data at {@link #uri} is the full stream. False otherwise. An example where this
|
||||
* may be false is if {@link #uri} defines the location of a cached part of the stream.
|
||||
*/
|
||||
public final boolean uriIsFullStream;
|
||||
/**
|
||||
* The absolute position of the data in the full stream.
|
||||
*/
|
||||
public final long absoluteStreamPosition;
|
||||
/**
|
||||
* The position of the data when read from {@link #uri}. Always equal to
|
||||
* {@link #absoluteStreamPosition} if {@link #uriIsFullStream}.
|
||||
* The position of the data when read from {@link #uri}.
|
||||
* <p>
|
||||
* Always equal to {@link #absoluteStreamPosition} unless the {@link #uri} defines the location
|
||||
* of a subset of the underyling data.
|
||||
*/
|
||||
public final long position;
|
||||
/**
|
||||
|
|
@ -52,6 +62,10 @@ public final class DataSpec {
|
|||
* {@link DataSpec} is not intended to be used in conjunction with a cache.
|
||||
*/
|
||||
public final String key;
|
||||
/**
|
||||
* Request flags. Currently {@link #FLAG_ALLOW_GZIP} is the only supported flag.
|
||||
*/
|
||||
public final int flags;
|
||||
|
||||
/**
|
||||
* Construct a {@link DataSpec} for the given uri and with {@link #key} set to null.
|
||||
|
|
@ -59,11 +73,21 @@ public final class DataSpec {
|
|||
* @param uri {@link #uri}.
|
||||
*/
|
||||
public DataSpec(Uri uri) {
|
||||
this(uri, 0, C.LENGTH_UNBOUNDED, null);
|
||||
this(uri, 0);
|
||||
}
|
||||
|
||||
/**
|
||||
* Construct a {@link DataSpec} for which {@link #uriIsFullStream} is true.
|
||||
* Construct a {@link DataSpec} for the given uri and with {@link #key} set to null.
|
||||
*
|
||||
* @param uri {@link #uri}.
|
||||
* @param flags {@link #flags}.
|
||||
*/
|
||||
public DataSpec(Uri uri, int flags) {
|
||||
this(uri, 0, C.LENGTH_UNBOUNDED, null, flags);
|
||||
}
|
||||
|
||||
/**
|
||||
* Construct a {@link DataSpec} where {@link #position} equals {@link #absoluteStreamPosition}.
|
||||
*
|
||||
* @param uri {@link #uri}.
|
||||
* @param absoluteStreamPosition {@link #absoluteStreamPosition}, equal to {@link #position}.
|
||||
|
|
@ -71,50 +95,50 @@ public final class DataSpec {
|
|||
* @param key {@link #key}.
|
||||
*/
|
||||
public DataSpec(Uri uri, long absoluteStreamPosition, long length, String key) {
|
||||
this(uri, absoluteStreamPosition, length, key, absoluteStreamPosition, true);
|
||||
this(uri, absoluteStreamPosition, absoluteStreamPosition, length, key, 0);
|
||||
}
|
||||
|
||||
/**
|
||||
* Construct a {@link DataSpec} for which {@link #uriIsFullStream} is false.
|
||||
* Construct a {@link DataSpec} where {@link #position} equals {@link #absoluteStreamPosition}.
|
||||
*
|
||||
* @param uri {@link #uri}.
|
||||
* @param absoluteStreamPosition {@link #absoluteStreamPosition}.
|
||||
* @param absoluteStreamPosition {@link #absoluteStreamPosition}, equal to {@link #position}.
|
||||
* @param length {@link #length}.
|
||||
* @param key {@link #key}.
|
||||
* @param position {@link #position}.
|
||||
* @param flags {@link #flags}.
|
||||
*/
|
||||
public DataSpec(Uri uri, long absoluteStreamPosition, long length, String key, long position) {
|
||||
this(uri, absoluteStreamPosition, length, key, position, false);
|
||||
public DataSpec(Uri uri, long absoluteStreamPosition, long length, String key, int flags) {
|
||||
this(uri, absoluteStreamPosition, absoluteStreamPosition, length, key, flags);
|
||||
}
|
||||
|
||||
/**
|
||||
* Construct a {@link DataSpec}.
|
||||
* Construct a {@link DataSpec} where {@link #position} may differ from
|
||||
* {@link #absoluteStreamPosition}.
|
||||
*
|
||||
* @param uri {@link #uri}.
|
||||
* @param absoluteStreamPosition {@link #absoluteStreamPosition}.
|
||||
* @param position {@link #position}.
|
||||
* @param length {@link #length}.
|
||||
* @param key {@link #key}.
|
||||
* @param position {@link #position}.
|
||||
* @param uriIsFullStream {@link #uriIsFullStream}.
|
||||
* @param flags {@link #flags}.
|
||||
*/
|
||||
public DataSpec(Uri uri, long absoluteStreamPosition, long length, String key, long position,
|
||||
boolean uriIsFullStream) {
|
||||
public DataSpec(Uri uri, long absoluteStreamPosition, long position, long length, String key,
|
||||
int flags) {
|
||||
Assertions.checkArgument(absoluteStreamPosition >= 0);
|
||||
Assertions.checkArgument(position >= 0);
|
||||
Assertions.checkArgument(length > 0 || length == C.LENGTH_UNBOUNDED);
|
||||
Assertions.checkArgument(absoluteStreamPosition == position || !uriIsFullStream);
|
||||
this.uri = uri;
|
||||
this.uriIsFullStream = uriIsFullStream;
|
||||
this.absoluteStreamPosition = absoluteStreamPosition;
|
||||
this.position = position;
|
||||
this.length = length;
|
||||
this.key = key;
|
||||
this.flags = flags;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "DataSpec[" + uri + ", " + uriIsFullStream + ", " + absoluteStreamPosition + ", " +
|
||||
position + ", " + length + ", " + key + "]";
|
||||
return "DataSpec[" + uri + ", " + ", " + absoluteStreamPosition + ", " +
|
||||
position + ", " + length + ", " + key + ", " + flags + "]";
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -132,19 +132,6 @@ public class DefaultHttpDataSource implements HttpDataSource {
|
|||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* TODO: If the server uses gzip compression when serving the response, this may end up returning
|
||||
* the size of the compressed response, where-as it should be returning the decompressed size or
|
||||
* -1. See: developer.android.com/reference/java/net/HttpURLConnection.html
|
||||
*
|
||||
* To fix this we should:
|
||||
*
|
||||
* 1. Explicitly require no compression for media requests (since media should be compressed
|
||||
* already) by setting the Accept-Encoding header to "identity"
|
||||
* 2. In other cases, for example when requesting manifests, we don't want to disable compression.
|
||||
* For these cases we should ensure that we return -1 here (and avoid performing any sanity
|
||||
* checks on the content length).
|
||||
*/
|
||||
@Override
|
||||
public long open(DataSpec dataSpec) throws HttpDataSourceException {
|
||||
this.dataSpec = dataSpec;
|
||||
|
|
@ -177,16 +164,23 @@ public class DefaultHttpDataSource implements HttpDataSource {
|
|||
throw new InvalidContentTypeException(contentType, dataSpec);
|
||||
}
|
||||
|
||||
long contentLength = getContentLength(connection);
|
||||
dataLength = dataSpec.length == C.LENGTH_UNBOUNDED ? contentLength : dataSpec.length;
|
||||
|
||||
if (dataSpec.length != C.LENGTH_UNBOUNDED && contentLength != C.LENGTH_UNBOUNDED
|
||||
&& contentLength != dataSpec.length) {
|
||||
// The DataSpec specified a length and we resolved a length from the response headers, but
|
||||
// the two lengths do not match.
|
||||
closeConnection();
|
||||
throw new HttpDataSourceException(
|
||||
new UnexpectedLengthException(dataSpec.length, contentLength), dataSpec);
|
||||
if ((dataSpec.flags & DataSpec.FLAG_ALLOW_GZIP) == 0) {
|
||||
long contentLength = getContentLength(connection);
|
||||
dataLength = dataSpec.length == C.LENGTH_UNBOUNDED ? contentLength : dataSpec.length;
|
||||
if (dataSpec.length != C.LENGTH_UNBOUNDED && contentLength != C.LENGTH_UNBOUNDED
|
||||
&& contentLength != dataSpec.length) {
|
||||
// The DataSpec specified a length and we resolved a length from the response headers, but
|
||||
// the two lengths do not match.
|
||||
closeConnection();
|
||||
throw new HttpDataSourceException(
|
||||
new UnexpectedLengthException(dataSpec.length, contentLength), dataSpec);
|
||||
}
|
||||
} else {
|
||||
// Gzip is enabled. If the server opts to use gzip then the content length in the response
|
||||
// will be that of the compressed data, which isn't what we want. Furthermore, there isn't a
|
||||
// reliable way to determine whether the gzip was used or not. Hence we always treat the
|
||||
// length as unknown.
|
||||
dataLength = C.LENGTH_UNBOUNDED;
|
||||
}
|
||||
|
||||
try {
|
||||
|
|
@ -301,6 +295,9 @@ public class DefaultHttpDataSource implements HttpDataSource {
|
|||
}
|
||||
setRangeHeader(connection, dataSpec);
|
||||
connection.setRequestProperty("User-Agent", userAgent);
|
||||
if ((dataSpec.flags & DataSpec.FLAG_ALLOW_GZIP) == 0) {
|
||||
connection.setRequestProperty("Accept-Encoding", "identity");
|
||||
}
|
||||
connection.connect();
|
||||
return connection;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -63,7 +63,7 @@ public final class NetworkLoadable<T> implements Loadable {
|
|||
public NetworkLoadable(String url, HttpDataSource httpDataSource, Parser<T> parser) {
|
||||
this.httpDataSource = httpDataSource;
|
||||
this.parser = parser;
|
||||
dataSpec = new DataSpec(Uri.parse(url));
|
||||
dataSpec = new DataSpec(Uri.parse(url), DataSpec.FLAG_ALLOW_GZIP);
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
|||
|
|
@ -42,8 +42,8 @@ public final class TeeDataSource implements DataSource {
|
|||
long dataLength = upstream.open(dataSpec);
|
||||
if (dataSpec.length == C.LENGTH_UNBOUNDED && dataLength != C.LENGTH_UNBOUNDED) {
|
||||
// Reconstruct dataSpec in order to provide the resolved length to the sink.
|
||||
dataSpec = new DataSpec(dataSpec.uri, dataSpec.absoluteStreamPosition, dataLength,
|
||||
dataSpec.key, dataSpec.position, dataSpec.uriIsFullStream);
|
||||
dataSpec = new DataSpec(dataSpec.uri, dataSpec.absoluteStreamPosition, dataSpec.position,
|
||||
dataLength, dataSpec.key, dataSpec.flags);
|
||||
}
|
||||
dataSink.open(dataSpec);
|
||||
return dataLength;
|
||||
|
|
|
|||
|
|
@ -22,7 +22,6 @@ import com.google.android.exoplayer.upstream.DataSpec;
|
|||
import com.google.android.exoplayer.upstream.FileDataSource;
|
||||
import com.google.android.exoplayer.upstream.TeeDataSource;
|
||||
import com.google.android.exoplayer.upstream.cache.CacheDataSink.CacheDataSinkException;
|
||||
import com.google.android.exoplayer.util.Assertions;
|
||||
|
||||
import android.net.Uri;
|
||||
import android.util.Log;
|
||||
|
|
@ -64,6 +63,7 @@ public final class CacheDataSource implements DataSource {
|
|||
|
||||
private DataSource currentDataSource;
|
||||
private Uri uri;
|
||||
private int flags;
|
||||
private String key;
|
||||
private long readPosition;
|
||||
private long bytesRemaining;
|
||||
|
|
@ -125,9 +125,9 @@ public final class CacheDataSource implements DataSource {
|
|||
|
||||
@Override
|
||||
public long open(DataSpec dataSpec) throws IOException {
|
||||
Assertions.checkState(dataSpec.uriIsFullStream);
|
||||
try {
|
||||
uri = dataSpec.uri;
|
||||
flags = dataSpec.flags;
|
||||
key = dataSpec.key;
|
||||
readPosition = dataSpec.position;
|
||||
bytesRemaining = dataSpec.length;
|
||||
|
|
@ -201,19 +201,19 @@ public final class CacheDataSource implements DataSource {
|
|||
// The data is locked in the cache, or we're ignoring the cache. Bypass the cache and read
|
||||
// from upstream.
|
||||
currentDataSource = upstreamDataSource;
|
||||
dataSpec = new DataSpec(uri, readPosition, bytesRemaining, key);
|
||||
dataSpec = new DataSpec(uri, readPosition, bytesRemaining, key, flags);
|
||||
} else if (span.isCached) {
|
||||
// Data is cached, read from cache.
|
||||
Uri fileUri = Uri.fromFile(span.file);
|
||||
long filePosition = readPosition - span.position;
|
||||
long length = Math.min(span.length - filePosition, bytesRemaining);
|
||||
dataSpec = new DataSpec(fileUri, readPosition, length, key, filePosition);
|
||||
dataSpec = new DataSpec(fileUri, readPosition, filePosition, length, key, flags);
|
||||
currentDataSource = cacheReadDataSource;
|
||||
} else {
|
||||
// Data is not cached, and data is not locked, read from upstream with cache backing.
|
||||
lockedSpan = span;
|
||||
long length = span.isOpenEnded() ? bytesRemaining : Math.min(span.length, bytesRemaining);
|
||||
dataSpec = new DataSpec(uri, readPosition, length, key);
|
||||
dataSpec = new DataSpec(uri, readPosition, length, key, flags);
|
||||
currentDataSource = cacheWriteDataSource != null ? cacheWriteDataSource
|
||||
: upstreamDataSource;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,258 @@
|
|||
/*
|
||||
* 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.util;
|
||||
|
||||
import android.net.Uri;
|
||||
import android.text.TextUtils;
|
||||
|
||||
/**
|
||||
* Utility methods for manipulating URIs.
|
||||
*/
|
||||
public final class UriUtil {
|
||||
|
||||
/**
|
||||
* The length of arrays returned by {@link #getUriIndices(String)}.
|
||||
*/
|
||||
private static final int INDEX_COUNT = 4;
|
||||
/**
|
||||
* An index into an array returned by {@link #getUriIndices(String)}.
|
||||
* <p>
|
||||
* The value at this position in the array is the index of the ':' after the scheme. Equals -1 if
|
||||
* the URI is a relative reference (no scheme). The hier-part starts at (schemeColon + 1),
|
||||
* including when the URI has no scheme.
|
||||
*/
|
||||
private static final int SCHEME_COLON = 0;
|
||||
/**
|
||||
* An index into an array returned by {@link #getUriIndices(String)}.
|
||||
* <p>
|
||||
* The value at this position in the array is the index of the path part. Equals (schemeColon + 1)
|
||||
* if no authority part, (schemeColon + 3) if the authority part consists of just "//", and
|
||||
* (query) if no path part. The characters starting at this index can be "//" only if the
|
||||
* authority part is non-empty (in this case the double-slash means the first segment is empty).
|
||||
*/
|
||||
private static final int PATH = 1;
|
||||
/**
|
||||
* An index into an array returned by {@link #getUriIndices(String)}.
|
||||
* <p>
|
||||
* The value at this position in the array is the index of the query part, including the '?'
|
||||
* before the query. Equals fragment if no query part, and (fragment - 1) if the query part is a
|
||||
* single '?' with no data.
|
||||
*/
|
||||
private static final int QUERY = 2;
|
||||
/**
|
||||
* An index into an array returned by {@link #getUriIndices(String)}.
|
||||
* <p>
|
||||
* The value at this position in the array is the index of the fragment part, including the '#'
|
||||
* before the fragment. Equal to the length of the URI if no fragment part, and (length - 1) if
|
||||
* the fragment part is a single '#' with no data.
|
||||
*/
|
||||
private static final int FRAGMENT = 3;
|
||||
|
||||
private UriUtil() {}
|
||||
|
||||
/**
|
||||
* Like {@link #resolve(String, String)}, but returns a {@link Uri} instead of a {@link String}.
|
||||
*
|
||||
* @param baseUri The base URI.
|
||||
* @param referenceUri The reference URI to resolve.
|
||||
*/
|
||||
public static Uri resolveToUri(String baseUri, String referenceUri) {
|
||||
return Uri.parse(resolve(baseUri, referenceUri));
|
||||
}
|
||||
|
||||
/**
|
||||
* Performs relative resolution of a {@code referenceUri} with respect to a {@code baseUri}.
|
||||
* <p>
|
||||
* The resolution is performed as specified by RFC-3986.
|
||||
*
|
||||
* @param baseUri The base URI.
|
||||
* @param referenceUri The reference URI to resolve.
|
||||
*/
|
||||
public static String resolve(String baseUri, String referenceUri) {
|
||||
StringBuilder uri = new StringBuilder();
|
||||
|
||||
// Map null onto empty string, to make the following logic simpler.
|
||||
baseUri = baseUri == null ? "" : baseUri;
|
||||
referenceUri = referenceUri == null ? "" : referenceUri;
|
||||
|
||||
int[] refIndices = getUriIndices(referenceUri);
|
||||
if (refIndices[SCHEME_COLON] != -1) {
|
||||
// The reference is absolute. The target Uri is the reference.
|
||||
uri.append(referenceUri);
|
||||
removeDotSegments(uri, refIndices[PATH], refIndices[QUERY]);
|
||||
return uri.toString();
|
||||
}
|
||||
|
||||
int[] baseIndices = getUriIndices(baseUri);
|
||||
if (refIndices[FRAGMENT] == 0) {
|
||||
// The reference is empty or contains just the fragment part, then the target Uri is the
|
||||
// concatenation of the base Uri without its fragment, and the reference.
|
||||
return uri.append(baseUri, 0, baseIndices[FRAGMENT]).append(referenceUri).toString();
|
||||
}
|
||||
|
||||
if (refIndices[QUERY] == 0) {
|
||||
// The reference starts with the query part. The target is the base up to (but excluding) the
|
||||
// query, plus the reference.
|
||||
return uri.append(baseUri, 0, baseIndices[QUERY]).append(referenceUri).toString();
|
||||
}
|
||||
|
||||
if (refIndices[PATH] != 0) {
|
||||
// The reference has authority. The target is the base scheme plus the reference.
|
||||
int baseLimit = baseIndices[SCHEME_COLON] + 1;
|
||||
uri.append(baseUri, 0, baseLimit).append(referenceUri);
|
||||
return removeDotSegments(uri, baseLimit + refIndices[PATH], baseLimit + refIndices[QUERY]);
|
||||
}
|
||||
|
||||
if (refIndices[PATH] != refIndices[QUERY] && referenceUri.charAt(refIndices[PATH]) == '/') {
|
||||
// The reference path is rooted. The target is the base scheme and authority (if any), plus
|
||||
// the reference.
|
||||
uri.append(baseUri, 0, baseIndices[PATH]).append(referenceUri);
|
||||
return removeDotSegments(uri, baseIndices[PATH], baseIndices[PATH] + refIndices[QUERY]);
|
||||
}
|
||||
|
||||
// The target Uri is the concatenation of the base Uri up to (but excluding) the last segment,
|
||||
// and the reference. This can be split into 2 cases:
|
||||
if (baseIndices[SCHEME_COLON] + 2 < baseIndices[PATH]
|
||||
&& baseIndices[PATH] == baseIndices[QUERY]) {
|
||||
// Case 1: The base hier-part is just the authority, with an empty path. An additional '/' is
|
||||
// needed after the authority, before appending the reference.
|
||||
uri.append(baseUri, 0, baseIndices[PATH]).append('/').append(referenceUri);
|
||||
return removeDotSegments(uri, baseIndices[PATH], baseIndices[PATH] + refIndices[QUERY] + 1);
|
||||
} else {
|
||||
// Case 2: Otherwise, find the last '/' in the base hier-part and append the reference after
|
||||
// it. If base hier-part has no '/', it could only mean that it is completely empty or
|
||||
// contains only one segment, in which case the whole hier-part is excluded and the reference
|
||||
// is appended right after the base scheme colon without an added '/'.
|
||||
int lastSlashIndex = baseUri.lastIndexOf('/', baseIndices[QUERY] - 1);
|
||||
int baseLimit = lastSlashIndex == -1 ? baseIndices[PATH] : lastSlashIndex + 1;
|
||||
uri.append(baseUri, 0, baseLimit).append(referenceUri);
|
||||
return removeDotSegments(uri, baseIndices[PATH], baseLimit + refIndices[QUERY]);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes dot segments from the path of a URI.
|
||||
*
|
||||
* @param uri A {@link StringBuilder} containing the URI.
|
||||
* @param offset The index of the start of the path in {@code uri}.
|
||||
* @param limit The limit (exclusive) of the path in {@code uri}.
|
||||
*/
|
||||
private static String removeDotSegments(StringBuilder uri, int offset, int limit) {
|
||||
if (offset >= limit) {
|
||||
// Nothing to do.
|
||||
return uri.toString();
|
||||
}
|
||||
if (uri.charAt(offset) == '/') {
|
||||
// If the path starts with a /, always retain it.
|
||||
offset++;
|
||||
}
|
||||
// The first character of the current path segment.
|
||||
int segmentStart = offset;
|
||||
int i = offset;
|
||||
while (i <= limit) {
|
||||
int nextSegmentStart = -1;
|
||||
if (i == limit) {
|
||||
nextSegmentStart = i;
|
||||
} else if (uri.charAt(i) == '/') {
|
||||
nextSegmentStart = i + 1;
|
||||
} else {
|
||||
i++;
|
||||
continue;
|
||||
}
|
||||
// We've encountered the end of a segment or the end of the path. If the final segment was
|
||||
// "." or "..", remove the appropriate segments of the path.
|
||||
if (i == segmentStart + 1 && uri.charAt(segmentStart) == '.') {
|
||||
// Given "abc/def/./ghi", remove "./" to get "abc/def/ghi".
|
||||
uri.delete(segmentStart, nextSegmentStart);
|
||||
limit -= nextSegmentStart - segmentStart;
|
||||
i = segmentStart;
|
||||
} else if (i == segmentStart + 2 && uri.charAt(segmentStart) == '.'
|
||||
&& uri.charAt(segmentStart + 1) == '.') {
|
||||
// Given "abc/def/../ghi", remove "def/../" to get "abc/ghi".
|
||||
int prevSegmentStart = uri.lastIndexOf("/", segmentStart - 2) + 1;
|
||||
int removeFrom = prevSegmentStart > offset ? prevSegmentStart : offset;
|
||||
uri.delete(removeFrom, nextSegmentStart);
|
||||
limit -= nextSegmentStart - removeFrom;
|
||||
segmentStart = prevSegmentStart;
|
||||
i = prevSegmentStart;
|
||||
} else {
|
||||
i++;
|
||||
segmentStart = i;
|
||||
}
|
||||
}
|
||||
return uri.toString();
|
||||
}
|
||||
|
||||
/**
|
||||
* Calculates indices of the constituent components of a URI.
|
||||
*
|
||||
* @param uriString The URI as a string.
|
||||
* @return The corresponding indices.
|
||||
*/
|
||||
private static int[] getUriIndices(String uriString) {
|
||||
int[] indices = new int[INDEX_COUNT];
|
||||
if (TextUtils.isEmpty(uriString)) {
|
||||
indices[SCHEME_COLON] = -1;
|
||||
return indices;
|
||||
}
|
||||
|
||||
// Determine outer structure from right to left.
|
||||
// Uri = scheme ":" hier-part [ "?" query ] [ "#" fragment ]
|
||||
int length = uriString.length();
|
||||
int fragmentIndex = uriString.indexOf('#');
|
||||
if (fragmentIndex == -1) {
|
||||
fragmentIndex = length;
|
||||
}
|
||||
int queryIndex = uriString.indexOf('?');
|
||||
if (queryIndex == -1 || queryIndex > fragmentIndex) {
|
||||
// '#' before '?': '?' is within the fragment.
|
||||
queryIndex = fragmentIndex;
|
||||
}
|
||||
// Slashes are allowed only in hier-part so any colon after the first slash is part of the
|
||||
// hier-part, not the scheme colon separator.
|
||||
int schemeIndexLimit = uriString.indexOf('/');
|
||||
if (schemeIndexLimit == -1 || schemeIndexLimit > queryIndex) {
|
||||
schemeIndexLimit = queryIndex;
|
||||
}
|
||||
int schemeIndex = uriString.indexOf(':');
|
||||
if (schemeIndex > schemeIndexLimit) {
|
||||
// '/' before ':'
|
||||
schemeIndex = -1;
|
||||
}
|
||||
|
||||
// Determine hier-part structure: hier-part = "//" authority path / path
|
||||
// This block can also cope with schemeIndex == -1.
|
||||
boolean hasAuthority = schemeIndex + 2 < queryIndex
|
||||
&& uriString.charAt(schemeIndex + 1) == '/'
|
||||
&& uriString.charAt(schemeIndex + 2) == '/';
|
||||
int pathIndex;
|
||||
if (hasAuthority) {
|
||||
pathIndex = uriString.indexOf('/', schemeIndex + 3); // find first '/' after "://"
|
||||
if (pathIndex == -1 || pathIndex > queryIndex) {
|
||||
pathIndex = queryIndex;
|
||||
}
|
||||
} else {
|
||||
pathIndex = schemeIndex + 1;
|
||||
}
|
||||
|
||||
indices[SCHEME_COLON] = schemeIndex;
|
||||
indices[PATH] = pathIndex;
|
||||
indices[QUERY] = queryIndex;
|
||||
indices[FRAGMENT] = fragmentIndex;
|
||||
return indices;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -17,7 +17,6 @@ package com.google.android.exoplayer.util;
|
|||
|
||||
import com.google.android.exoplayer.upstream.DataSource;
|
||||
|
||||
import android.net.Uri;
|
||||
import android.text.TextUtils;
|
||||
|
||||
import java.io.IOException;
|
||||
|
|
@ -134,54 +133,6 @@ public final class Util {
|
|||
return text == null ? null : text.toLowerCase(Locale.US);
|
||||
}
|
||||
|
||||
/**
|
||||
* Like {@link Uri#parse(String)}, but discards the part of the uri that follows the final
|
||||
* forward slash.
|
||||
*
|
||||
* @param uriString An RFC 2396-compliant, encoded uri.
|
||||
* @return The parsed base uri.
|
||||
*/
|
||||
public static Uri parseBaseUri(String uriString) {
|
||||
return Uri.parse(uriString.substring(0, uriString.lastIndexOf('/')));
|
||||
}
|
||||
|
||||
/**
|
||||
* Merges a uri and a string to produce a new uri.
|
||||
* <p>
|
||||
* The uri is built according to the following rules:
|
||||
* <ul>
|
||||
* <li>If {@code baseUri} is null or if {@code stringUri} is absolute, then {@code baseUri} is
|
||||
* ignored and the uri consists solely of {@code stringUri}.
|
||||
* <li>If {@code stringUri} is null, then the uri consists solely of {@code baseUrl}.
|
||||
* <li>Otherwise, the uri consists of the concatenation of {@code baseUri} and {@code stringUri}.
|
||||
* </ul>
|
||||
*
|
||||
* @param baseUri A uri that can form the base of the merged uri.
|
||||
* @param stringUri A relative or absolute uri in string form.
|
||||
* @return The merged uri.
|
||||
*/
|
||||
public static Uri getMergedUri(Uri baseUri, String stringUri) {
|
||||
if (stringUri == null) {
|
||||
return baseUri;
|
||||
}
|
||||
if (baseUri == null) {
|
||||
return Uri.parse(stringUri);
|
||||
}
|
||||
if (stringUri.startsWith("/")) {
|
||||
stringUri = stringUri.substring(1);
|
||||
return new Uri.Builder()
|
||||
.scheme(baseUri.getScheme())
|
||||
.authority(baseUri.getAuthority())
|
||||
.appendEncodedPath(stringUri)
|
||||
.build();
|
||||
}
|
||||
Uri uri = Uri.parse(stringUri);
|
||||
if (uri.isAbsolute()) {
|
||||
return uri;
|
||||
}
|
||||
return Uri.withAppendedPath(baseUri, stringUri);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the index of the largest value in an array that is less than (or optionally equal to)
|
||||
* a specified key.
|
||||
|
|
|
|||
10
library/src/test/.classpath
Normal file
10
library/src/test/.classpath
Normal file
|
|
@ -0,0 +1,10 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<classpath>
|
||||
<classpathentry kind="src" path="gen"/>
|
||||
<classpathentry kind="src" path="java"/>
|
||||
<classpathentry kind="con" path="com.android.ide.eclipse.adt.ANDROID_FRAMEWORK"/>
|
||||
<classpathentry exported="true" kind="con" path="com.android.ide.eclipse.adt.LIBRARIES"/>
|
||||
<classpathentry exported="true" kind="con" path="com.android.ide.eclipse.adt.DEPENDENCIES"/>
|
||||
<classpathentry combineaccessrules="false" kind="src" path="/ExoPlayerDemo"/>
|
||||
<classpathentry kind="output" path="bin/classes"/>
|
||||
</classpath>
|
||||
62
library/src/test/.project
Normal file
62
library/src/test/.project
Normal file
|
|
@ -0,0 +1,62 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<projectDescription>
|
||||
<name>ExoPlayerTests</name>
|
||||
<comment></comment>
|
||||
<projects>
|
||||
<project>ExoPlayerLib</project>
|
||||
</projects>
|
||||
<buildSpec>
|
||||
<buildCommand>
|
||||
<name>com.android.ide.eclipse.adt.ResourceManagerBuilder</name>
|
||||
<arguments>
|
||||
</arguments>
|
||||
</buildCommand>
|
||||
<buildCommand>
|
||||
<name>com.android.ide.eclipse.adt.PreCompilerBuilder</name>
|
||||
<arguments>
|
||||
</arguments>
|
||||
</buildCommand>
|
||||
<buildCommand>
|
||||
<name>org.eclipse.jdt.core.javabuilder</name>
|
||||
<arguments>
|
||||
</arguments>
|
||||
</buildCommand>
|
||||
<buildCommand>
|
||||
<name>com.android.ide.eclipse.adt.ApkBuilder</name>
|
||||
<arguments>
|
||||
</arguments>
|
||||
</buildCommand>
|
||||
</buildSpec>
|
||||
<natures>
|
||||
<nature>com.android.ide.eclipse.adt.AndroidNature</nature>
|
||||
<nature>org.eclipse.jdt.core.javanature</nature>
|
||||
</natures>
|
||||
<linkedResources>
|
||||
<link>
|
||||
<name>libs/dexmaker-1.2.jar</name>
|
||||
<type>1</type>
|
||||
<locationURI>$%7BPARENT-3-PROJECT_LOC%7D/third_party/dexmaker/dexmaker-1.2.jar</locationURI>
|
||||
</link>
|
||||
<link>
|
||||
<name>libs/dexmaker-mockito-1.2.jar</name>
|
||||
<type>1</type>
|
||||
<locationURI>$%7BPARENT-3-PROJECT_LOC%7D/third_party/dexmaker/dexmaker-mockito-1.2.jar</locationURI>
|
||||
</link>
|
||||
<link>
|
||||
<name>libs/mockito-all-1.9.5.jar</name>
|
||||
<type>1</type>
|
||||
<locationURI>$%7BPARENT-3-PROJECT_LOC%7D/third_party/mockito/mockito-all-1.9.5.jar</locationURI>
|
||||
</link>
|
||||
</linkedResources>
|
||||
<filteredResources>
|
||||
<filter>
|
||||
<id>1425657306619</id>
|
||||
<name></name>
|
||||
<type>14</type>
|
||||
<matcher>
|
||||
<id>org.eclipse.ui.ide.multiFilter</id>
|
||||
<arguments>1.0-name-matches-true-false-BUILD</arguments>
|
||||
</matcher>
|
||||
</filter>
|
||||
</filteredResources>
|
||||
</projectDescription>
|
||||
30
library/src/test/AndroidManifest.xml
Normal file
30
library/src/test/AndroidManifest.xml
Normal file
|
|
@ -0,0 +1,30 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!-- 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.
|
||||
-->
|
||||
|
||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
package="com.google.android.exoplayer.tests">
|
||||
|
||||
<uses-sdk android:minSdkVersion="9" android:targetSdkVersion="21"/>
|
||||
|
||||
<application>
|
||||
<uses-library android:name="android.test.runner"/>
|
||||
</application>
|
||||
|
||||
<instrumentation
|
||||
android:targetPackage="com.google.android.exoplayer.demo"
|
||||
android:name="android.test.InstrumentationTestRunner"/>
|
||||
|
||||
</manifest>
|
||||
99
library/src/test/assets/dash/sample_mpd_1
Executable file
99
library/src/test/assets/dash/sample_mpd_1
Executable file
|
|
@ -0,0 +1,99 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<MPD xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
availabilityStartTime="2014-06-19T23:07:42"
|
||||
minBufferTime="PT1.500S"
|
||||
minimumUpdatePeriod="PT5.000S"
|
||||
profiles="urn:mpeg:dash:profile:isoff-main:2011"
|
||||
timeShiftBufferDepth="PT129600.000S"
|
||||
type="dynamic"
|
||||
xmlns="urn:mpeg:DASH:schema:MPD:2011"
|
||||
xsi:schemaLocation="urn:mpeg:DASH:schema:MPD:2011 DASH-MPD.xsd"
|
||||
yt:earliestMediaSequence="1266404" >
|
||||
<Period start="PT6462826.784S" >
|
||||
<SegmentList
|
||||
presentationTimeOffset="34740095"
|
||||
startNumber="1292317"
|
||||
timescale="1000" >
|
||||
<SegmentTimeline>
|
||||
<S d="4804" />
|
||||
<S d="5338" />
|
||||
<S d="4938" />
|
||||
</SegmentTimeline>
|
||||
</SegmentList>
|
||||
<AdaptationSet
|
||||
mimeType="audio/mp4"
|
||||
subsegmentAlignment="true" >
|
||||
<Role
|
||||
schemeIdUri="urn:mpeg:DASH:role:2011"
|
||||
value="main" />
|
||||
<Representation
|
||||
id="141"
|
||||
audioSamplingRate="48000"
|
||||
bandwidth="272000"
|
||||
codecs="mp4a.40.2"
|
||||
startWithSAP="1" >
|
||||
<AudioChannelConfiguration
|
||||
schemeIdUri="urn:mpeg:dash:23003:3:audio_channel_configuration:2011"
|
||||
value="2" />
|
||||
<BaseURL>
|
||||
http://www.test.com/141
|
||||
</BaseURL>
|
||||
<SegmentList>
|
||||
<Initialization
|
||||
range="0-591"
|
||||
sourceURL="sq/0/clen/79480/lmt/1403219262956762/dur/4.805" />
|
||||
<SegmentURL media="sq/1292317/clen/77447/lmt/1409671169987621/dur/4.805" />
|
||||
<SegmentURL media="sq/1292318/clen/86958/lmt/1409671174832549/dur/5.339" />
|
||||
<SegmentURL media="sq/1292319/clen/85018/lmt/1409671179719956/dur/4.938" />
|
||||
</SegmentList>
|
||||
</Representation>
|
||||
</AdaptationSet>
|
||||
<AdaptationSet
|
||||
mimeType="video/mp4"
|
||||
subsegmentAlignment="true" >
|
||||
<Role
|
||||
schemeIdUri="urn:mpeg:DASH:role:2011"
|
||||
value="main" />
|
||||
<Representation
|
||||
id="135"
|
||||
bandwidth="1116000"
|
||||
codecs="avc1.42c01f"
|
||||
height="480"
|
||||
startWithSAP="1"
|
||||
width="854" >
|
||||
<BaseURL>
|
||||
http://www.test.com/135
|
||||
</BaseURL>
|
||||
<SegmentList>
|
||||
<Initialization
|
||||
range="0-671"
|
||||
sourceURL="sq/0/clen/1221137/lmt/1403219262956762/dur/4.805" />
|
||||
<SegmentURL media="sq/1292317/clen/1279915/lmt/1409671169987621/dur/4.805" />
|
||||
<SegmentURL media="sq/1292318/clen/1310650/lmt/1409671174832549/dur/5.339" />
|
||||
<SegmentURL media="sq/1292319/clen/1486558/lmt/1409671179719956/dur/4.938" />
|
||||
</SegmentList>
|
||||
</Representation>
|
||||
</AdaptationSet>
|
||||
<AdaptationSet
|
||||
lang="en"
|
||||
mimeType="text/vtt" >
|
||||
<Role
|
||||
schemeIdUri="urn:mpeg:DASH:role:2011"
|
||||
value="caption" />
|
||||
<Representation
|
||||
id="en"
|
||||
bandwidth="0"
|
||||
codecs="" >
|
||||
<BaseURL>
|
||||
http://www.test.com/vtt
|
||||
</BaseURL>
|
||||
<SegmentList>
|
||||
<SegmentURL media="sq/1292317" />
|
||||
<SegmentURL media="sq/1292318" />
|
||||
<SegmentURL media="sq/1292319" />
|
||||
</SegmentList>
|
||||
</Representation>
|
||||
</AdaptationSet>
|
||||
</Period>
|
||||
</MPD>
|
||||
|
||||
BIN
library/src/test/assets/webm/vorbis_codec_private
Normal file
BIN
library/src/test/assets/webm/vorbis_codec_private
Normal file
Binary file not shown.
1
library/src/test/assets/webvtt/empty
Normal file
1
library/src/test/assets/webvtt/empty
Normal file
|
|
@ -0,0 +1 @@
|
|||
|
||||
8
library/src/test/assets/webvtt/typical
Normal file
8
library/src/test/assets/webvtt/typical
Normal file
|
|
@ -0,0 +1,8 @@
|
|||
WEBVTT
|
||||
X-TIMESTAMP-MAP=LOCAL:00:00.000,MPEGTS:450000
|
||||
|
||||
00:00.000 --> 00:01.234
|
||||
This is the first subtitle.
|
||||
|
||||
00:02.345 --> 00:03.456
|
||||
This is the second subtitle.
|
||||
10
library/src/test/assets/webvtt/typical_with_identifiers
Normal file
10
library/src/test/assets/webvtt/typical_with_identifiers
Normal file
|
|
@ -0,0 +1,10 @@
|
|||
WEBVTT
|
||||
X-TIMESTAMP-MAP=LOCAL:00:00.000,MPEGTS:450000
|
||||
|
||||
1
|
||||
00:00.000 --> 00:01.234
|
||||
This is the first subtitle.
|
||||
|
||||
2
|
||||
00:02.345 --> 00:03.456
|
||||
This is the second subtitle.
|
||||
14
library/src/test/assets/webvtt/typical_with_tags
Normal file
14
library/src/test/assets/webvtt/typical_with_tags
Normal file
|
|
@ -0,0 +1,14 @@
|
|||
WEBVTT
|
||||
X-TIMESTAMP-MAP=LOCAL:00:00.000,MPEGTS:450000
|
||||
|
||||
00:00.000 --> 00:01.234
|
||||
This is the <i>first</i> subtitle.
|
||||
|
||||
00:02.345 --> 00:03.456
|
||||
This is the <b><i>second</b></i> subtitle.
|
||||
|
||||
00:04.000 --> 00:05.000
|
||||
This is the <c.red.caps>third</c> subtitle.
|
||||
|
||||
00:06.000 --> 00:07.000
|
||||
This is the <fourth> &subtitle.
|
||||
|
|
@ -0,0 +1,60 @@
|
|||
/*
|
||||
* 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;
|
||||
|
||||
import com.google.android.exoplayer.util.Util;
|
||||
|
||||
import android.annotation.TargetApi;
|
||||
|
||||
import junit.framework.TestCase;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* Unit test for {@link MediaFormat}.
|
||||
*/
|
||||
public class MediaFormatTest extends TestCase {
|
||||
|
||||
public void testConversionToFrameworkFormat() {
|
||||
if (Util.SDK_INT < 16) {
|
||||
// Test doesn't apply.
|
||||
return;
|
||||
}
|
||||
|
||||
byte[] initData1 = new byte[] {1, 2, 3};
|
||||
byte[] initData2 = new byte[] {4, 5, 6};
|
||||
List<byte[]> initData = new ArrayList<byte[]>();
|
||||
initData.add(initData1);
|
||||
initData.add(initData2);
|
||||
|
||||
testConversionToFrameworkFormatV16(
|
||||
MediaFormat.createVideoFormat("video/xyz", 102400, 1280, 720, 1.5f, initData));
|
||||
testConversionToFrameworkFormatV16(
|
||||
MediaFormat.createAudioFormat("audio/xyz", 102400, 5, 44100, initData));
|
||||
}
|
||||
|
||||
@TargetApi(16)
|
||||
private void testConversionToFrameworkFormatV16(MediaFormat format) {
|
||||
// Convert to a framework MediaFormat and back again.
|
||||
MediaFormat convertedFormat = MediaFormat.createFromFrameworkMediaFormatV16(
|
||||
format.getFrameworkMediaFormatV16());
|
||||
// Assert that we end up with an equivalent object to the one we started with.
|
||||
assertEquals(format.hashCode(), convertedFormat.hashCode());
|
||||
assertEquals(format, convertedFormat);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,350 @@
|
|||
/*
|
||||
* 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.chunk.parser.webm;
|
||||
|
||||
import com.google.android.exoplayer.ParserException;
|
||||
import com.google.android.exoplayer.upstream.ByteArrayNonBlockingInputStream;
|
||||
import com.google.android.exoplayer.upstream.NonBlockingInputStream;
|
||||
|
||||
import junit.framework.TestCase;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* Tests {@link DefaultEbmlReader}.
|
||||
*/
|
||||
public class DefaultEbmlReaderTest extends TestCase {
|
||||
|
||||
private final EventCapturingEbmlEventHandler eventHandler =
|
||||
new EventCapturingEbmlEventHandler();
|
||||
|
||||
public void testNothing() {
|
||||
NonBlockingInputStream input = createTestInputStream();
|
||||
assertNoEvents(input, EbmlReader.READ_RESULT_END_OF_STREAM);
|
||||
}
|
||||
|
||||
public void testMasterElement() {
|
||||
NonBlockingInputStream input =
|
||||
createTestInputStream(0x1A, 0x45, 0xDF, 0xA3, 0x84, 0x42, 0x85, 0x81, 0x01);
|
||||
EventCapturingEbmlEventHandler expected = new EventCapturingEbmlEventHandler();
|
||||
expected.onMasterElementStart(EventCapturingEbmlEventHandler.ID_EBML, 0, 5, 4);
|
||||
expected.onIntegerElement(EventCapturingEbmlEventHandler.ID_DOC_TYPE_READ_VERSION, 1);
|
||||
expected.onMasterElementEnd(EventCapturingEbmlEventHandler.ID_EBML);
|
||||
assertEvents(input, EbmlReader.READ_RESULT_END_OF_STREAM, expected.events);
|
||||
}
|
||||
|
||||
public void testMasterElementEmpty() {
|
||||
NonBlockingInputStream input = createTestInputStream(0x18, 0x53, 0x80, 0x67, 0x80);
|
||||
EventCapturingEbmlEventHandler expected = new EventCapturingEbmlEventHandler();
|
||||
expected.onMasterElementStart(EventCapturingEbmlEventHandler.ID_SEGMENT, 0, 5, 0);
|
||||
expected.onMasterElementEnd(EventCapturingEbmlEventHandler.ID_SEGMENT);
|
||||
assertEvents(input, EbmlReader.READ_RESULT_END_OF_STREAM, expected.events);
|
||||
}
|
||||
|
||||
public void testUnsignedIntegerElement() {
|
||||
// 0xFE is chosen because for signed integers it should be interpreted as -2
|
||||
NonBlockingInputStream input = createTestInputStream(0x42, 0xF7, 0x81, 0xFE);
|
||||
EventCapturingEbmlEventHandler expected = new EventCapturingEbmlEventHandler();
|
||||
expected.onIntegerElement(EventCapturingEbmlEventHandler.ID_EBML_READ_VERSION, 254);
|
||||
assertEvents(input, EbmlReader.READ_RESULT_END_OF_STREAM, expected.events);
|
||||
}
|
||||
|
||||
public void testUnsignedIntegerElementLarge() {
|
||||
NonBlockingInputStream input =
|
||||
createTestInputStream(0x42, 0xF7, 0x88, 0x7F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF);
|
||||
EventCapturingEbmlEventHandler expected = new EventCapturingEbmlEventHandler();
|
||||
expected.onIntegerElement(EventCapturingEbmlEventHandler.ID_EBML_READ_VERSION, Long.MAX_VALUE);
|
||||
assertEvents(input, EbmlReader.READ_RESULT_END_OF_STREAM, expected.events);
|
||||
}
|
||||
|
||||
public void testUnsignedIntegerElementTooLargeBecomesNegative() {
|
||||
NonBlockingInputStream input =
|
||||
createTestInputStream(0x42, 0xF7, 0x88, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF);
|
||||
EventCapturingEbmlEventHandler expected = new EventCapturingEbmlEventHandler();
|
||||
expected.onIntegerElement(EventCapturingEbmlEventHandler.ID_EBML_READ_VERSION, -1);
|
||||
assertEvents(input, EbmlReader.READ_RESULT_END_OF_STREAM, expected.events);
|
||||
}
|
||||
|
||||
public void testStringElement() {
|
||||
NonBlockingInputStream input =
|
||||
createTestInputStream(0x42, 0x82, 0x86, 0x41, 0x62, 0x63, 0x31, 0x32, 0x33);
|
||||
EventCapturingEbmlEventHandler expected = new EventCapturingEbmlEventHandler();
|
||||
expected.onStringElement(EventCapturingEbmlEventHandler.ID_DOC_TYPE, "Abc123");
|
||||
assertEvents(input, EbmlReader.READ_RESULT_END_OF_STREAM, expected.events);
|
||||
}
|
||||
|
||||
public void testStringElementEmpty() {
|
||||
NonBlockingInputStream input = createTestInputStream(0x42, 0x82, 0x80);
|
||||
EventCapturingEbmlEventHandler expected = new EventCapturingEbmlEventHandler();
|
||||
expected.onStringElement(EventCapturingEbmlEventHandler.ID_DOC_TYPE, "");
|
||||
assertEvents(input, EbmlReader.READ_RESULT_END_OF_STREAM, expected.events);
|
||||
}
|
||||
|
||||
public void testFloatElementThreeBytes() {
|
||||
try {
|
||||
eventHandler.read(createTestInputStream(0x44, 0x89, 0x83, 0x3F, 0x80, 0x00));
|
||||
fail();
|
||||
} catch (IllegalStateException exception) {
|
||||
// Expected
|
||||
}
|
||||
assertNoEvents();
|
||||
}
|
||||
|
||||
public void testFloatElementFourBytes() {
|
||||
NonBlockingInputStream input =
|
||||
createTestInputStream(0x44, 0x89, 0x84, 0x3F, 0x80, 0x00, 0x00);
|
||||
EventCapturingEbmlEventHandler expected = new EventCapturingEbmlEventHandler();
|
||||
expected.onFloatElement(EventCapturingEbmlEventHandler.ID_DURATION, 1.0);
|
||||
assertEvents(input, EbmlReader.READ_RESULT_END_OF_STREAM, expected.events);
|
||||
}
|
||||
|
||||
public void testFloatElementEightBytes() {
|
||||
NonBlockingInputStream input =
|
||||
createTestInputStream(0x44, 0x89, 0x88, 0xC0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00);
|
||||
|
||||
EventCapturingEbmlEventHandler expected = new EventCapturingEbmlEventHandler();
|
||||
expected.onFloatElement(EventCapturingEbmlEventHandler.ID_DURATION, -2.0);
|
||||
assertEvents(input, EbmlReader.READ_RESULT_END_OF_STREAM, expected.events);
|
||||
}
|
||||
|
||||
public void testBinaryElementReadBytes() {
|
||||
eventHandler.binaryElementHandler = EventCapturingEbmlEventHandler.HANDLER_READ_BYTES;
|
||||
NonBlockingInputStream input =
|
||||
createTestInputStream(0xA3, 0x88, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08);
|
||||
|
||||
EventCapturingEbmlEventHandler expected = new EventCapturingEbmlEventHandler();
|
||||
expected.binaryElementHandler = EventCapturingEbmlEventHandler.HANDLER_READ_BYTES;
|
||||
expected.onBinaryElement(
|
||||
EventCapturingEbmlEventHandler.ID_SIMPLE_BLOCK, 0, 0, 8,
|
||||
createTestInputStream(0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08));
|
||||
assertEvents(input, EbmlReader.READ_RESULT_END_OF_STREAM, expected.events);
|
||||
}
|
||||
|
||||
public void testBinaryElementReadVarint() {
|
||||
eventHandler.binaryElementHandler = EventCapturingEbmlEventHandler.HANDLER_READ_VARINT;
|
||||
NonBlockingInputStream input = createTestInputStream(0xA3, 0x82, 0x40, 0x2A);
|
||||
|
||||
EventCapturingEbmlEventHandler expected = new EventCapturingEbmlEventHandler();
|
||||
expected.binaryElementHandler = EventCapturingEbmlEventHandler.HANDLER_READ_VARINT;
|
||||
expected.onBinaryElement(
|
||||
EventCapturingEbmlEventHandler.ID_SIMPLE_BLOCK, 0, 0, 0,
|
||||
createTestInputStream(0x40, 0x2A));
|
||||
assertEvents(input, EbmlReader.READ_RESULT_END_OF_STREAM, expected.events);
|
||||
}
|
||||
|
||||
public void testBinaryElementSkipBytes() {
|
||||
eventHandler.binaryElementHandler = EventCapturingEbmlEventHandler.HANDLER_SKIP_BYTES;
|
||||
NonBlockingInputStream input =
|
||||
createTestInputStream(0xA3, 0x88, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08);
|
||||
|
||||
EventCapturingEbmlEventHandler expected = new EventCapturingEbmlEventHandler();
|
||||
expected.binaryElementHandler = EventCapturingEbmlEventHandler.HANDLER_SKIP_BYTES;
|
||||
expected.onBinaryElement(
|
||||
EventCapturingEbmlEventHandler.ID_SIMPLE_BLOCK, 0, 0, 8,
|
||||
createTestInputStream(0, 0, 0, 0, 0, 0, 0, 0));
|
||||
assertEvents(input, EbmlReader.READ_RESULT_END_OF_STREAM, expected.events);
|
||||
}
|
||||
|
||||
public void testBinaryElementDoNothing() {
|
||||
eventHandler.binaryElementHandler = EventCapturingEbmlEventHandler.HANDLER_DO_NOTHING;
|
||||
try {
|
||||
eventHandler.read(
|
||||
createTestInputStream(0xA3, 0x88, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08));
|
||||
fail();
|
||||
} catch (IllegalStateException exception) {
|
||||
// Expected
|
||||
}
|
||||
assertNoEvents();
|
||||
}
|
||||
|
||||
public void testBinaryElementNotEnoughBytes() {
|
||||
NonBlockingInputStream input = createTestInputStream(0xA3, 0x88, 0x01, 0x02, 0x03);
|
||||
assertNoEvents(input, EbmlReader.READ_RESULT_NEED_MORE_DATA);
|
||||
}
|
||||
|
||||
public void testUnknownElement() {
|
||||
NonBlockingInputStream input = createTestInputStream(0xEC, 0x81, 0x00);
|
||||
assertNoEvents(input, EbmlReader.READ_RESULT_END_OF_STREAM);
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper to build a {@link ByteArrayNonBlockingInputStream} quickly from zero or more
|
||||
* integer arguments.
|
||||
*
|
||||
* <p>Each argument must be able to cast to a byte value.
|
||||
*
|
||||
* @param data Zero or more integers with values between {@code 0x00} and {@code 0xFF}
|
||||
* @return A {@link ByteArrayNonBlockingInputStream} containing the given byte values
|
||||
*/
|
||||
private NonBlockingInputStream createTestInputStream(int... data) {
|
||||
byte[] bytes = new byte[data.length];
|
||||
for (int i = 0; i < data.length; i++) {
|
||||
bytes[i] = (byte) data[i];
|
||||
}
|
||||
return new ByteArrayNonBlockingInputStream(bytes);
|
||||
}
|
||||
|
||||
private void assertReads(NonBlockingInputStream input, int continues, int finalResult) {
|
||||
for (int i = 0; i < continues; i++) {
|
||||
assertEquals(EbmlReader.READ_RESULT_CONTINUE, eventHandler.read(input));
|
||||
}
|
||||
assertEquals(finalResult, eventHandler.read(input));
|
||||
}
|
||||
|
||||
private void assertNoEvents() {
|
||||
assertEvents(Collections.<String>emptyList());
|
||||
}
|
||||
|
||||
private void assertEvents(List<String> events) {
|
||||
assertEquals(events.size(), eventHandler.events.size());
|
||||
for (int i = 0; i < events.size(); i++) {
|
||||
assertEquals(events.get(i), eventHandler.events.get(i));
|
||||
}
|
||||
}
|
||||
|
||||
private void assertNoEvents(NonBlockingInputStream input, int finalResult) {
|
||||
assertReads(input, 0, finalResult);
|
||||
assertNoEvents();
|
||||
}
|
||||
|
||||
private void assertEvents(NonBlockingInputStream input, int finalResult, List<String> events) {
|
||||
assertReads(input, events.size(), finalResult);
|
||||
assertEvents(events);
|
||||
}
|
||||
|
||||
/**
|
||||
* An {@link EbmlEventHandler} which captures all event callbacks made by
|
||||
* {@link DefaultEbmlReader} for testing purposes.
|
||||
*/
|
||||
private static final class EventCapturingEbmlEventHandler implements EbmlEventHandler {
|
||||
|
||||
// Element IDs
|
||||
private static final int ID_EBML = 0x1A45DFA3;
|
||||
private static final int ID_EBML_READ_VERSION = 0x42F7;
|
||||
private static final int ID_DOC_TYPE = 0x4282;
|
||||
private static final int ID_DOC_TYPE_READ_VERSION = 0x4285;
|
||||
|
||||
private static final int ID_SEGMENT = 0x18538067;
|
||||
private static final int ID_DURATION = 0x4489;
|
||||
private static final int ID_SIMPLE_BLOCK = 0xA3;
|
||||
|
||||
// Various ways to handle things in onBinaryElement()
|
||||
private static final int HANDLER_DO_NOTHING = 0;
|
||||
private static final int HANDLER_READ_BYTES = 1;
|
||||
private static final int HANDLER_READ_VARINT = 2;
|
||||
private static final int HANDLER_SKIP_BYTES = 3;
|
||||
|
||||
private final EbmlReader reader = new DefaultEbmlReader();
|
||||
private final List<String> events = new ArrayList<String>();
|
||||
|
||||
private int binaryElementHandler;
|
||||
|
||||
private EventCapturingEbmlEventHandler() {
|
||||
reader.setEventHandler(this);
|
||||
}
|
||||
|
||||
private int read(NonBlockingInputStream inputStream) {
|
||||
try {
|
||||
return reader.read(inputStream);
|
||||
} catch (ParserException e) {
|
||||
// should never happen.
|
||||
fail();
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getElementType(int id) {
|
||||
switch (id) {
|
||||
case ID_EBML:
|
||||
case ID_SEGMENT:
|
||||
return EbmlReader.TYPE_MASTER;
|
||||
case ID_EBML_READ_VERSION:
|
||||
case ID_DOC_TYPE_READ_VERSION:
|
||||
return EbmlReader.TYPE_UNSIGNED_INT;
|
||||
case ID_DOC_TYPE:
|
||||
return EbmlReader.TYPE_STRING;
|
||||
case ID_SIMPLE_BLOCK:
|
||||
return EbmlReader.TYPE_BINARY;
|
||||
case ID_DURATION:
|
||||
return EbmlReader.TYPE_FLOAT;
|
||||
default:
|
||||
return EbmlReader.TYPE_UNKNOWN;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onMasterElementStart(
|
||||
int id, long elementOffset, int headerSize, long contentsSize) {
|
||||
events.add(formatEvent(id, "start elementOffset=" + elementOffset
|
||||
+ " headerSize=" + headerSize + " contentsSize=" + contentsSize));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onMasterElementEnd(int id) {
|
||||
events.add(formatEvent(id, "end"));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onIntegerElement(int id, long value) {
|
||||
events.add(formatEvent(id, "integer=" + String.valueOf(value)));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onFloatElement(int id, double value) {
|
||||
events.add(formatEvent(id, "float=" + String.valueOf(value)));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onStringElement(int id, String value) {
|
||||
events.add(formatEvent(id, "string=" + value));
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onBinaryElement(
|
||||
int id, long elementOffset, int headerSize, int contentsSize,
|
||||
NonBlockingInputStream inputStream) {
|
||||
switch (binaryElementHandler) {
|
||||
case HANDLER_READ_BYTES:
|
||||
byte[] bytes = new byte[contentsSize];
|
||||
reader.readBytes(inputStream, bytes, contentsSize);
|
||||
events.add(formatEvent(id, "bytes=" + Arrays.toString(bytes)));
|
||||
break;
|
||||
case HANDLER_READ_VARINT:
|
||||
long value = reader.readVarint(inputStream);
|
||||
events.add(formatEvent(id, "varint=" + String.valueOf(value)));
|
||||
break;
|
||||
case HANDLER_SKIP_BYTES:
|
||||
reader.skipBytes(inputStream, contentsSize);
|
||||
events.add(formatEvent(id, "skipped " + contentsSize + " byte(s)"));
|
||||
break;
|
||||
case HANDLER_DO_NOTHING:
|
||||
default:
|
||||
// pass
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
private static String formatEvent(int id, String event) {
|
||||
return "[" + Integer.toHexString(id) + "] " + event;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,523 @@
|
|||
/*
|
||||
* 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.chunk.parser.webm;
|
||||
|
||||
import com.google.android.exoplayer.C;
|
||||
import com.google.android.exoplayer.MediaFormat;
|
||||
import com.google.android.exoplayer.ParserException;
|
||||
import com.google.android.exoplayer.SampleHolder;
|
||||
import com.google.android.exoplayer.chunk.parser.SegmentIndex;
|
||||
import com.google.android.exoplayer.upstream.ByteArrayNonBlockingInputStream;
|
||||
import com.google.android.exoplayer.upstream.NonBlockingInputStream;
|
||||
import com.google.android.exoplayer.util.MimeTypes;
|
||||
|
||||
import android.test.InstrumentationTestCase;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.nio.ByteBuffer;
|
||||
import java.util.Arrays;
|
||||
|
||||
public class WebmExtractorTest extends InstrumentationTestCase {
|
||||
|
||||
private static final int INFO_ELEMENT_BYTE_SIZE = 31;
|
||||
private static final int TRACKS_ELEMENT_BYTE_SIZE = 48;
|
||||
private static final int CUES_ELEMENT_BYTE_SIZE = 12;
|
||||
private static final int CUE_POINT_ELEMENT_BYTE_SIZE = 31;
|
||||
|
||||
private static final int DEFAULT_TIMECODE_SCALE = 1000000;
|
||||
|
||||
private static final long TEST_DURATION_US = 9920000L;
|
||||
private static final int TEST_WIDTH = 1280;
|
||||
private static final int TEST_HEIGHT = 720;
|
||||
private static final int TEST_CHANNEL_COUNT = 1;
|
||||
private static final int TEST_SAMPLE_RATE = 48000;
|
||||
private static final long TEST_CODEC_DELAY = 6500000;
|
||||
private static final long TEST_SEEK_PRE_ROLL = 80000000;
|
||||
private static final int TEST_OPUS_CODEC_PRIVATE_SIZE = 2;
|
||||
private static final String TEST_VORBIS_CODEC_PRIVATE = "webm/vorbis_codec_private";
|
||||
private static final int TEST_VORBIS_INFO_SIZE = 30;
|
||||
private static final int TEST_VORBIS_BOOKS_SIZE = 4140;
|
||||
|
||||
private static final int ID_VP9 = 0;
|
||||
private static final int ID_OPUS = 1;
|
||||
private static final int ID_VORBIS = 2;
|
||||
|
||||
private static final int EXPECTED_INIT_RESULT = WebmExtractor.RESULT_READ_INIT
|
||||
| WebmExtractor.RESULT_READ_INDEX | WebmExtractor.RESULT_END_OF_STREAM;
|
||||
private static final int EXPECTED_INIT_AND_SAMPLE_RESULT = WebmExtractor.RESULT_READ_INIT
|
||||
| WebmExtractor.RESULT_READ_INDEX | WebmExtractor.RESULT_READ_SAMPLE;
|
||||
|
||||
private final WebmExtractor extractor = new WebmExtractor();
|
||||
private final SampleHolder sampleHolder =
|
||||
new SampleHolder(SampleHolder.BUFFER_REPLACEMENT_MODE_DISABLED);
|
||||
|
||||
@Override
|
||||
public void setUp() {
|
||||
sampleHolder.data = ByteBuffer.allocate(1024);
|
||||
}
|
||||
|
||||
public void testPrepare() throws ParserException {
|
||||
NonBlockingInputStream testInputStream = new ByteArrayNonBlockingInputStream(
|
||||
createInitializationSegment(1, 0, true, DEFAULT_TIMECODE_SCALE, ID_VP9));
|
||||
assertEquals(EXPECTED_INIT_RESULT, extractor.read(testInputStream, sampleHolder));
|
||||
assertFormat();
|
||||
assertIndex(new IndexPoint(0, 0, TEST_DURATION_US));
|
||||
}
|
||||
|
||||
public void testPrepareOpus() throws ParserException {
|
||||
NonBlockingInputStream testInputStream = new ByteArrayNonBlockingInputStream(
|
||||
createInitializationSegment(1, 0, true, DEFAULT_TIMECODE_SCALE, ID_OPUS));
|
||||
assertEquals(EXPECTED_INIT_RESULT, extractor.read(testInputStream, sampleHolder));
|
||||
assertAudioFormat(ID_OPUS);
|
||||
assertIndex(new IndexPoint(0, 0, TEST_DURATION_US));
|
||||
}
|
||||
|
||||
public void testPrepareVorbis() throws ParserException {
|
||||
NonBlockingInputStream testInputStream = new ByteArrayNonBlockingInputStream(
|
||||
createInitializationSegment(1, 0, true, DEFAULT_TIMECODE_SCALE, ID_VORBIS));
|
||||
assertEquals(EXPECTED_INIT_RESULT, extractor.read(testInputStream, sampleHolder));
|
||||
assertAudioFormat(ID_VORBIS);
|
||||
assertIndex(new IndexPoint(0, 0, TEST_DURATION_US));
|
||||
}
|
||||
|
||||
public void testPrepareThreeCuePoints() throws ParserException {
|
||||
NonBlockingInputStream testInputStream = new ByteArrayNonBlockingInputStream(
|
||||
createInitializationSegment(3, 0, true, DEFAULT_TIMECODE_SCALE, ID_VP9));
|
||||
assertEquals(EXPECTED_INIT_RESULT, extractor.read(testInputStream, sampleHolder));
|
||||
assertFormat();
|
||||
assertIndex(
|
||||
new IndexPoint(0, 0, 10000),
|
||||
new IndexPoint(10000, 0, 10000),
|
||||
new IndexPoint(20000, 0, TEST_DURATION_US - 20000));
|
||||
}
|
||||
|
||||
public void testPrepareCustomTimecodeScale() throws ParserException {
|
||||
NonBlockingInputStream testInputStream = new ByteArrayNonBlockingInputStream(
|
||||
createInitializationSegment(3, 0, true, 1000, ID_VP9));
|
||||
assertEquals(EXPECTED_INIT_RESULT, extractor.read(testInputStream, sampleHolder));
|
||||
assertFormat();
|
||||
assertIndex(
|
||||
new IndexPoint(0, 0, 10),
|
||||
new IndexPoint(10, 0, 10),
|
||||
new IndexPoint(20, 0, (TEST_DURATION_US / 1000) - 20));
|
||||
}
|
||||
|
||||
public void testPrepareNoCuePoints() {
|
||||
NonBlockingInputStream testInputStream = new ByteArrayNonBlockingInputStream(
|
||||
createInitializationSegment(0, 0, true, DEFAULT_TIMECODE_SCALE, ID_VP9));
|
||||
try {
|
||||
extractor.read(testInputStream, sampleHolder);
|
||||
fail();
|
||||
} catch (ParserException exception) {
|
||||
assertEquals("Invalid/missing cue points", exception.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
public void testPrepareInvalidDocType() {
|
||||
NonBlockingInputStream testInputStream = new ByteArrayNonBlockingInputStream(
|
||||
createInitializationSegment(1, 0, false, DEFAULT_TIMECODE_SCALE, ID_VP9));
|
||||
try {
|
||||
extractor.read(testInputStream, sampleHolder);
|
||||
fail();
|
||||
} catch (ParserException exception) {
|
||||
assertEquals("DocType webB not supported", exception.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
public void testReadSampleKeyframe() throws ParserException {
|
||||
MediaSegment mediaSegment = createMediaSegment(100, 0, 0, true, false, true);
|
||||
byte[] testInputData = joinByteArrays(
|
||||
createInitializationSegment(
|
||||
1, mediaSegment.clusterBytes.length, true, DEFAULT_TIMECODE_SCALE, ID_VP9),
|
||||
mediaSegment.clusterBytes);
|
||||
NonBlockingInputStream testInputStream = new ByteArrayNonBlockingInputStream(testInputData);
|
||||
assertEquals(EXPECTED_INIT_AND_SAMPLE_RESULT, extractor.read(testInputStream, sampleHolder));
|
||||
assertFormat();
|
||||
assertSample(mediaSegment, 0, true, false);
|
||||
assertEquals(WebmExtractor.RESULT_END_OF_STREAM, extractor.read(testInputStream, sampleHolder));
|
||||
}
|
||||
|
||||
public void testReadBlock() throws ParserException {
|
||||
MediaSegment mediaSegment = createMediaSegment(100, 0, 0, true, false, false);
|
||||
byte[] testInputData = joinByteArrays(
|
||||
createInitializationSegment(
|
||||
1, mediaSegment.clusterBytes.length, true, DEFAULT_TIMECODE_SCALE, ID_OPUS),
|
||||
mediaSegment.clusterBytes);
|
||||
NonBlockingInputStream testInputStream = new ByteArrayNonBlockingInputStream(testInputData);
|
||||
assertEquals(EXPECTED_INIT_AND_SAMPLE_RESULT, extractor.read(testInputStream, sampleHolder));
|
||||
assertAudioFormat(ID_OPUS);
|
||||
assertSample(mediaSegment, 0, true, false);
|
||||
assertEquals(WebmExtractor.RESULT_END_OF_STREAM, extractor.read(testInputStream, sampleHolder));
|
||||
}
|
||||
|
||||
public void testReadSampleInvisible() throws ParserException {
|
||||
MediaSegment mediaSegment = createMediaSegment(100, 12, 13, false, true, true);
|
||||
byte[] testInputData = joinByteArrays(
|
||||
createInitializationSegment(
|
||||
1, mediaSegment.clusterBytes.length, true, DEFAULT_TIMECODE_SCALE, ID_VP9),
|
||||
mediaSegment.clusterBytes);
|
||||
NonBlockingInputStream testInputStream = new ByteArrayNonBlockingInputStream(testInputData);
|
||||
assertEquals(EXPECTED_INIT_AND_SAMPLE_RESULT, extractor.read(testInputStream, sampleHolder));
|
||||
assertFormat();
|
||||
assertSample(mediaSegment, 25000, false, true);
|
||||
assertEquals(WebmExtractor.RESULT_END_OF_STREAM, extractor.read(testInputStream, sampleHolder));
|
||||
}
|
||||
|
||||
public void testReadSampleCustomTimescale() throws ParserException {
|
||||
MediaSegment mediaSegment = createMediaSegment(100, 12, 13, false, false, true);
|
||||
byte[] testInputData = joinByteArrays(
|
||||
createInitializationSegment(
|
||||
1, mediaSegment.clusterBytes.length, true, 1000, ID_VP9),
|
||||
mediaSegment.clusterBytes);
|
||||
NonBlockingInputStream testInputStream = new ByteArrayNonBlockingInputStream(testInputData);
|
||||
assertEquals(EXPECTED_INIT_AND_SAMPLE_RESULT, extractor.read(testInputStream, sampleHolder));
|
||||
assertFormat();
|
||||
assertSample(mediaSegment, 25, false, false);
|
||||
assertEquals(WebmExtractor.RESULT_END_OF_STREAM, extractor.read(testInputStream, sampleHolder));
|
||||
}
|
||||
|
||||
public void testReadSampleNegativeSimpleBlockTimecode() throws ParserException {
|
||||
MediaSegment mediaSegment = createMediaSegment(100, 13, -12, true, true, true);
|
||||
byte[] testInputData = joinByteArrays(
|
||||
createInitializationSegment(
|
||||
1, mediaSegment.clusterBytes.length, true, DEFAULT_TIMECODE_SCALE, ID_VP9),
|
||||
mediaSegment.clusterBytes);
|
||||
NonBlockingInputStream testInputStream = new ByteArrayNonBlockingInputStream(testInputData);
|
||||
assertEquals(EXPECTED_INIT_AND_SAMPLE_RESULT, extractor.read(testInputStream, sampleHolder));
|
||||
assertFormat();
|
||||
assertSample(mediaSegment, 1000, true, true);
|
||||
assertEquals(WebmExtractor.RESULT_END_OF_STREAM, extractor.read(testInputStream, sampleHolder));
|
||||
}
|
||||
|
||||
private void assertFormat() {
|
||||
MediaFormat format = extractor.getFormat();
|
||||
assertEquals(TEST_WIDTH, format.width);
|
||||
assertEquals(TEST_HEIGHT, format.height);
|
||||
assertEquals(MimeTypes.VIDEO_VP9, format.mimeType);
|
||||
}
|
||||
|
||||
private void assertAudioFormat(int codecId) {
|
||||
MediaFormat format = extractor.getFormat();
|
||||
assertEquals(TEST_CHANNEL_COUNT, format.channelCount);
|
||||
assertEquals(TEST_SAMPLE_RATE, format.sampleRate);
|
||||
if (codecId == ID_OPUS) {
|
||||
assertEquals(MimeTypes.AUDIO_OPUS, format.mimeType);
|
||||
assertEquals(3, format.initializationData.size());
|
||||
assertEquals(TEST_OPUS_CODEC_PRIVATE_SIZE, format.initializationData.get(0).length);
|
||||
assertEquals(TEST_CODEC_DELAY, ByteBuffer.wrap(format.initializationData.get(1)).getLong());
|
||||
assertEquals(TEST_SEEK_PRE_ROLL, ByteBuffer.wrap(format.initializationData.get(2)).getLong());
|
||||
} else if (codecId == ID_VORBIS) {
|
||||
assertEquals(MimeTypes.AUDIO_VORBIS, format.mimeType);
|
||||
assertEquals(2, format.initializationData.size());
|
||||
assertEquals(TEST_VORBIS_INFO_SIZE, format.initializationData.get(0).length);
|
||||
assertEquals(TEST_VORBIS_BOOKS_SIZE, format.initializationData.get(1).length);
|
||||
}
|
||||
}
|
||||
|
||||
private void assertIndex(IndexPoint... indexPoints) {
|
||||
SegmentIndex index = extractor.getIndex();
|
||||
assertEquals(CUES_ELEMENT_BYTE_SIZE + CUE_POINT_ELEMENT_BYTE_SIZE * indexPoints.length,
|
||||
index.sizeBytes);
|
||||
assertEquals(indexPoints.length, index.length);
|
||||
for (int i = 0; i < indexPoints.length; i++) {
|
||||
IndexPoint indexPoint = indexPoints[i];
|
||||
assertEquals(indexPoint.timeUs, index.timesUs[i]);
|
||||
assertEquals(indexPoint.size, index.sizes[i]);
|
||||
assertEquals(indexPoint.durationUs, index.durationsUs[i]);
|
||||
}
|
||||
}
|
||||
|
||||
private void assertSample(
|
||||
MediaSegment mediaSegment, int timeUs, boolean keyframe, boolean invisible) {
|
||||
assertTrue(Arrays.equals(
|
||||
mediaSegment.videoBytes, Arrays.copyOf(sampleHolder.data.array(), sampleHolder.size)));
|
||||
assertEquals(timeUs, sampleHolder.timeUs);
|
||||
assertEquals(keyframe, (sampleHolder.flags & C.SAMPLE_FLAG_SYNC) != 0);
|
||||
assertEquals(invisible, sampleHolder.decodeOnly);
|
||||
}
|
||||
|
||||
private byte[] createInitializationSegment(
|
||||
int cuePoints, int mediaSegmentSize, boolean docTypeIsWebm, int timecodeScale,
|
||||
int codecId) {
|
||||
int initalizationSegmentSize = INFO_ELEMENT_BYTE_SIZE + TRACKS_ELEMENT_BYTE_SIZE
|
||||
+ CUES_ELEMENT_BYTE_SIZE + CUE_POINT_ELEMENT_BYTE_SIZE * cuePoints;
|
||||
byte[] tracksElement = null;
|
||||
switch (codecId) {
|
||||
case ID_VP9:
|
||||
tracksElement = createTracksElementWithVideo(true, TEST_WIDTH, TEST_HEIGHT);
|
||||
break;
|
||||
case ID_OPUS:
|
||||
tracksElement = createTracksElementWithOpusAudio(TEST_CHANNEL_COUNT);
|
||||
break;
|
||||
case ID_VORBIS:
|
||||
tracksElement = createTracksElementWithVorbisAudio(TEST_CHANNEL_COUNT);
|
||||
break;
|
||||
}
|
||||
byte[] bytes = joinByteArrays(createEbmlElement(1, docTypeIsWebm, 2),
|
||||
createSegmentElement(initalizationSegmentSize + mediaSegmentSize),
|
||||
createInfoElement(timecodeScale),
|
||||
tracksElement,
|
||||
createCuesElement(CUE_POINT_ELEMENT_BYTE_SIZE * cuePoints));
|
||||
for (int i = 0; i < cuePoints; i++) {
|
||||
bytes = joinByteArrays(bytes, createCuePointElement(10 * i, initalizationSegmentSize));
|
||||
}
|
||||
return bytes;
|
||||
}
|
||||
|
||||
private static MediaSegment createMediaSegment(int videoBytesLength, int clusterTimecode,
|
||||
int blockTimecode, boolean keyframe, boolean invisible, boolean isSimple) {
|
||||
byte[] videoBytes = createVideoBytes(videoBytesLength);
|
||||
byte[] blockBytes;
|
||||
if (isSimple) {
|
||||
blockBytes = createSimpleBlockElement(videoBytes.length, blockTimecode,
|
||||
keyframe, invisible, true);
|
||||
} else {
|
||||
blockBytes = createBlockElement(videoBytes.length, blockTimecode, invisible, true);
|
||||
}
|
||||
byte[] clusterBytes =
|
||||
createClusterElement(blockBytes.length + videoBytes.length, clusterTimecode);
|
||||
return new MediaSegment(joinByteArrays(clusterBytes, blockBytes, videoBytes), videoBytes);
|
||||
}
|
||||
|
||||
private static byte[] joinByteArrays(byte[]... byteArrays) {
|
||||
int length = 0;
|
||||
for (byte[] byteArray : byteArrays) {
|
||||
length += byteArray.length;
|
||||
}
|
||||
byte[] joined = new byte[length];
|
||||
length = 0;
|
||||
for (byte[] byteArray : byteArrays) {
|
||||
System.arraycopy(byteArray, 0, joined, length, byteArray.length);
|
||||
length += byteArray.length;
|
||||
}
|
||||
return joined;
|
||||
}
|
||||
|
||||
private static byte[] createEbmlElement(
|
||||
int ebmlReadVersion, boolean docTypeIsWebm, int docTypeReadVersion) {
|
||||
return createByteArray(
|
||||
0x1A, 0x45, 0xDF, 0xA3, // EBML
|
||||
0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0F, // size=15
|
||||
0x42, 0xF7, // EBMLReadVersion
|
||||
0x81, ebmlReadVersion, // size=1
|
||||
0x42, 0x82, // DocType
|
||||
0x84, 0x77, 0x65, 0x62, docTypeIsWebm ? 0x6D : 0x42, // size=4 value=webm/B
|
||||
0x42, 0x85, // DocTypeReadVersion
|
||||
0x81, docTypeReadVersion); // size=1
|
||||
}
|
||||
|
||||
private static byte[] createSegmentElement(int size) {
|
||||
byte[] sizeBytes = getIntegerBytes(size);
|
||||
return createByteArray(
|
||||
0x18, 0x53, 0x80, 0x67, // Segment
|
||||
0x01, 0x00, 0x00, 0x00, sizeBytes[0], sizeBytes[1], sizeBytes[2], sizeBytes[3]);
|
||||
}
|
||||
|
||||
private static byte[] createInfoElement(int timecodeScale) {
|
||||
byte[] scaleBytes = getIntegerBytes(timecodeScale);
|
||||
return createByteArray(
|
||||
0x15, 0x49, 0xA9, 0x66, // Info
|
||||
0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x13, // size=19
|
||||
0x2A, 0xD7, 0xB1, // TimecodeScale
|
||||
0x84, scaleBytes[0], scaleBytes[1], scaleBytes[2], scaleBytes[3], // size=4
|
||||
0x44, 0x89, // Duration
|
||||
0x88, 0x40, 0xC3, 0x60, 0x00, 0x00, 0x00, 0x00, 0x00); // size=8 value=9920.0
|
||||
}
|
||||
|
||||
private static byte[] createTracksElementWithVideo(
|
||||
boolean codecIsVp9, int pixelWidth, int pixelHeight) {
|
||||
byte[] widthBytes = getIntegerBytes(pixelWidth);
|
||||
byte[] heightBytes = getIntegerBytes(pixelHeight);
|
||||
return createByteArray(
|
||||
0x16, 0x54, 0xAE, 0x6B, // Tracks
|
||||
0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x24, // size=36
|
||||
0xAE, // TrackEntry
|
||||
0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x1B, // size=27
|
||||
0x86, // CodecID
|
||||
0x85, 0x56, 0x5F, 0x56, 0x50, codecIsVp9 ? 0x39 : 0x30, // size=5 value=V_VP9/0
|
||||
0xE0, // Video
|
||||
0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x08, // size=8
|
||||
0xB0, // PixelWidth
|
||||
0x82, widthBytes[2], widthBytes[3], // size=2
|
||||
0xBA, // PixelHeight
|
||||
0x82, heightBytes[2], heightBytes[3]); // size=2
|
||||
}
|
||||
|
||||
private static byte[] createTracksElementWithOpusAudio(int channelCount) {
|
||||
byte[] channelCountBytes = getIntegerBytes(channelCount);
|
||||
return createByteArray(
|
||||
0x16, 0x54, 0xAE, 0x6B, // Tracks
|
||||
0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x39, // size=57
|
||||
0xAE, // TrackEntry
|
||||
0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x30, // size=48
|
||||
0x86, // CodecID
|
||||
0x86, 0x41, 0x5F, 0x4F, 0x50, 0x55, 0x53, // size=6 value=A_OPUS
|
||||
0x56, 0xAA, // CodecDelay
|
||||
0x83, 0x63, 0x2E, 0xA0, // size=3 value=6500000
|
||||
0x56, 0xBB, // SeekPreRoll
|
||||
0x84, 0x04, 0xC4, 0xB4, 0x00, // size=4 value=80000000
|
||||
0xE1, // Audio
|
||||
0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0D, // size=13
|
||||
0x9F, // Channels
|
||||
0x81, channelCountBytes[3], // size=1
|
||||
0xB5, // SamplingFrequency
|
||||
0x88, 0x40, 0xE7, 0x70, 0x00, 0x00, 0x00, 0x00, 0x00, // size=8 value=48000
|
||||
0x63, 0xA2, // CodecPrivate
|
||||
0x82, 0x00, 0x00); // size=2
|
||||
}
|
||||
|
||||
private byte[] createTracksElementWithVorbisAudio(int channelCount) {
|
||||
byte[] channelCountBytes = getIntegerBytes(channelCount);
|
||||
byte[] tracksElement = createByteArray(
|
||||
0x16, 0x54, 0xAE, 0x6B, // Tracks
|
||||
0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x10, 0x9C, // size=4252
|
||||
0xAE, // TrackEntry
|
||||
0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x10, 0x93, // size=4243 (36+4207)
|
||||
0x86, // CodecID
|
||||
0x88, 0x41, 0x5f, 0x56, 0x4f, 0x52, 0x42, 0x49, 0x53, // size=8 value=A_VORBIS
|
||||
0xE1, // Audio
|
||||
0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0D, // size=13
|
||||
0x9F, // Channels
|
||||
0x81, channelCountBytes[3], // size=1
|
||||
0xB5, // SamplingFrequency
|
||||
0x88, 0x40, 0xE7, 0x70, 0x00, 0x00, 0x00, 0x00, 0x00, // size=8 value=48000
|
||||
0x63, 0xA2, // CodecPrivate
|
||||
0x50, 0x6F); // size=4207
|
||||
byte[] codecPrivate = new byte[4207];
|
||||
try {
|
||||
getInstrumentation().getContext().getResources().getAssets().open(TEST_VORBIS_CODEC_PRIVATE)
|
||||
.read(codecPrivate);
|
||||
} catch (IOException e) {
|
||||
fail(); // should never happen
|
||||
}
|
||||
return joinByteArrays(tracksElement, codecPrivate);
|
||||
}
|
||||
|
||||
private static byte[] createCuesElement(int size) {
|
||||
byte[] sizeBytes = getIntegerBytes(size);
|
||||
return createByteArray(
|
||||
0x1C, 0x53, 0xBB, 0x6B, // Cues
|
||||
0x01, 0x00, 0x00, 0x00, sizeBytes[0], sizeBytes[1], sizeBytes[2], sizeBytes[3]); // size=31
|
||||
}
|
||||
|
||||
private static byte[] createCuePointElement(int cueTime, int cueClusterPosition) {
|
||||
byte[] positionBytes = getIntegerBytes(cueClusterPosition);
|
||||
return createByteArray(
|
||||
0xBB, // CuePoint
|
||||
0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x16, // size=22
|
||||
0xB3, // CueTime
|
||||
0x81, cueTime, // size=1
|
||||
0xB7, // CueTrackPositions
|
||||
0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0A, // size=10
|
||||
0xF1, // CueClusterPosition
|
||||
0x88, 0x00, 0x00, 0x00, 0x00, positionBytes[0], positionBytes[1],
|
||||
positionBytes[2], positionBytes[3]); // size=8
|
||||
}
|
||||
|
||||
private static byte[] createClusterElement(int size, int timecode) {
|
||||
byte[] sizeBytes = getIntegerBytes(size);
|
||||
byte[] timeBytes = getIntegerBytes(timecode);
|
||||
return createByteArray(
|
||||
0x1F, 0x43, 0xB6, 0x75, // Cluster
|
||||
0x01, 0x00, 0x00, 0x00, sizeBytes[0], sizeBytes[1], sizeBytes[2], sizeBytes[3],
|
||||
0xE7, // Timecode
|
||||
0x84, timeBytes[0], timeBytes[1], timeBytes[2], timeBytes[3]); // size=4
|
||||
}
|
||||
|
||||
private static byte[] createSimpleBlockElement(
|
||||
int size, int timecode, boolean keyframe, boolean invisible, boolean noLacing) {
|
||||
byte[] sizeBytes = getIntegerBytes(size + 4);
|
||||
byte[] timeBytes = getIntegerBytes(timecode);
|
||||
byte flags = (byte)
|
||||
((keyframe ? 0x80 : 0x00) | (invisible ? 0x08 : 0x00) | (noLacing ? 0x00 : 0x06));
|
||||
return createByteArray(
|
||||
0xA3, // SimpleBlock
|
||||
0x01, 0x00, 0x00, 0x00, sizeBytes[0], sizeBytes[1], sizeBytes[2], sizeBytes[3],
|
||||
0x81, // Track number value=1
|
||||
timeBytes[2], timeBytes[3], flags); // Timecode and flags
|
||||
}
|
||||
|
||||
private static byte[] createBlockElement(
|
||||
int size, int timecode, boolean invisible, boolean noLacing) {
|
||||
int blockSize = size + 4;
|
||||
byte[] blockSizeBytes = getIntegerBytes(blockSize);
|
||||
byte[] timeBytes = getIntegerBytes(timecode);
|
||||
int blockElementSize = 1 + 8 + blockSize; // id + size + length of data
|
||||
byte[] sizeBytes = getIntegerBytes(blockElementSize);
|
||||
byte flags = (byte) ((invisible ? 0x08 : 0x00) | (noLacing ? 0x00 : 0x06));
|
||||
return createByteArray(
|
||||
0xA0, // BlockGroup
|
||||
0x01, 0x00, 0x00, 0x00, sizeBytes[0], sizeBytes[1], sizeBytes[2], sizeBytes[3],
|
||||
0xA1, // Block
|
||||
0x01, 0x00, 0x00, 0x00,
|
||||
blockSizeBytes[0], blockSizeBytes[1], blockSizeBytes[2], blockSizeBytes[3],
|
||||
0x81, // Track number value=1
|
||||
timeBytes[2], timeBytes[3], flags); // Timecode and flags
|
||||
}
|
||||
|
||||
private static byte[] createVideoBytes(int size) {
|
||||
byte[] videoBytes = new byte[size];
|
||||
for (int i = 0; i < size; i++) {
|
||||
videoBytes[i] = (byte) i;
|
||||
}
|
||||
return videoBytes;
|
||||
}
|
||||
|
||||
private static byte[] getIntegerBytes(int value) {
|
||||
return createByteArray(
|
||||
(value & 0xFF000000) >> 24,
|
||||
(value & 0x00FF0000) >> 16,
|
||||
(value & 0x0000FF00) >> 8,
|
||||
(value & 0x000000FF));
|
||||
}
|
||||
|
||||
private static byte[] createByteArray(int... intArray) {
|
||||
byte[] byteArray = new byte[intArray.length];
|
||||
for (int i = 0; i < byteArray.length; i++) {
|
||||
byteArray[i] = (byte) intArray[i];
|
||||
}
|
||||
return byteArray;
|
||||
}
|
||||
|
||||
/** Used by {@link #createMediaSegment} to return both cluster and video bytes together. */
|
||||
private static final class MediaSegment {
|
||||
|
||||
private final byte[] clusterBytes;
|
||||
private final byte[] videoBytes;
|
||||
|
||||
private MediaSegment(byte[] clusterBytes, byte[] videoBytes) {
|
||||
this.clusterBytes = clusterBytes;
|
||||
this.videoBytes = videoBytes;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/** Used by {@link #assertIndex(IndexPoint...)} to validate index elements. */
|
||||
private static final class IndexPoint {
|
||||
|
||||
private final long timeUs;
|
||||
private final int size;
|
||||
private final long durationUs;
|
||||
|
||||
private IndexPoint(long timeUs, int size, long durationUs) {
|
||||
this.timeUs = timeUs;
|
||||
this.size = size;
|
||||
this.durationUs = durationUs;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,49 @@
|
|||
/*
|
||||
* 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;
|
||||
|
||||
import com.google.android.exoplayer.MediaFormat;
|
||||
import com.google.android.exoplayer.chunk.Format;
|
||||
import com.google.android.exoplayer.dash.mpd.Representation;
|
||||
import com.google.android.exoplayer.dash.mpd.SegmentBase.SingleSegmentBase;
|
||||
|
||||
import junit.framework.TestCase;
|
||||
|
||||
/**
|
||||
* Tests {@link DashChunkSource}.
|
||||
*/
|
||||
public class DashChunkSourceTest extends TestCase {
|
||||
|
||||
public void testMaxVideoDimensions() {
|
||||
SingleSegmentBase segmentBase1 = new SingleSegmentBase("https://example.com/1.mp4");
|
||||
Format format1 = new Format("1", "video/mp4", 100, 200, -1, -1, 1000);
|
||||
Representation representation1 =
|
||||
Representation.newInstance(0, 0, null, 0, format1, segmentBase1);
|
||||
|
||||
SingleSegmentBase segmentBase2 = new SingleSegmentBase("https://example.com/2.mp4");
|
||||
Format format2 = new Format("2", "video/mp4", 400, 50, -1, -1, 1000);
|
||||
Representation representation2 =
|
||||
Representation.newInstance(0, 0, null, 0, format2, segmentBase2);
|
||||
|
||||
DashChunkSource chunkSource = new DashChunkSource(null, null, representation1, representation2);
|
||||
MediaFormat out = MediaFormat.createVideoFormat("video/h264", 1, 1, 1, 1, null);
|
||||
chunkSource.getMaxVideoDimensions(out);
|
||||
|
||||
assertEquals(400, out.getMaxVideoWidth());
|
||||
assertEquals(200, out.getMaxVideoHeight());
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,38 @@
|
|||
/*
|
||||
* 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 android.test.InstrumentationTestCase;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
|
||||
/**
|
||||
* Unit tests for {@link MediaPresentationDescriptionParser}.
|
||||
*/
|
||||
public class MediaPresentationDescriptionParserTest extends InstrumentationTestCase {
|
||||
|
||||
private static final String SAMPLE_MPD_1 = "dash/sample_mpd_1";
|
||||
|
||||
public void testParseMediaPresentationDescription() throws IOException {
|
||||
MediaPresentationDescriptionParser parser = new MediaPresentationDescriptionParser();
|
||||
InputStream inputStream =
|
||||
getInstrumentation().getContext().getResources().getAssets().open(SAMPLE_MPD_1);
|
||||
// Simple test to ensure that the sample manifest parses without throwing any exceptions.
|
||||
parser.parse("https://example.com/test.mpd", inputStream);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,78 @@
|
|||
/*
|
||||
* 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 junit.framework.TestCase;
|
||||
|
||||
/**
|
||||
* Unit test for {@link RangedUri}.
|
||||
*/
|
||||
public class RangedUriTest extends TestCase {
|
||||
|
||||
private static final String FULL_URI = "http://www.test.com/path/file.ext";
|
||||
|
||||
public void testMerge() {
|
||||
RangedUri rangeA = new RangedUri(null, FULL_URI, 0, 10);
|
||||
RangedUri rangeB = new RangedUri(null, FULL_URI, 10, 10);
|
||||
RangedUri expected = new RangedUri(null, FULL_URI, 0, 20);
|
||||
assertMerge(rangeA, rangeB, expected);
|
||||
}
|
||||
|
||||
public void testMergeUnbounded() {
|
||||
RangedUri rangeA = new RangedUri(null, FULL_URI, 0, 10);
|
||||
RangedUri rangeB = new RangedUri(null, FULL_URI, 10, -1);
|
||||
RangedUri expected = new RangedUri(null, FULL_URI, 0, -1);
|
||||
assertMerge(rangeA, rangeB, expected);
|
||||
}
|
||||
|
||||
public void testNonMerge() {
|
||||
// A and B do not overlap, so should not merge
|
||||
RangedUri rangeA = new RangedUri(null, FULL_URI, 0, 10);
|
||||
RangedUri rangeB = new RangedUri(null, FULL_URI, 11, 10);
|
||||
assertNonMerge(rangeA, rangeB);
|
||||
|
||||
// A and B do not overlap, so should not merge
|
||||
rangeA = new RangedUri(null, FULL_URI, 0, 10);
|
||||
rangeB = new RangedUri(null, FULL_URI, 11, -1);
|
||||
assertNonMerge(rangeA, rangeB);
|
||||
|
||||
// A and B are bounded but overlap, so should not merge
|
||||
rangeA = new RangedUri(null, FULL_URI, 0, 11);
|
||||
rangeB = new RangedUri(null, FULL_URI, 10, 10);
|
||||
assertNonMerge(rangeA, rangeB);
|
||||
|
||||
// A and B overlap due to unboundedness, so should not merge
|
||||
rangeA = new RangedUri(null, FULL_URI, 0, -1);
|
||||
rangeB = new RangedUri(null, FULL_URI, 10, -1);
|
||||
assertNonMerge(rangeA, rangeB);
|
||||
|
||||
}
|
||||
|
||||
private void assertMerge(RangedUri rangeA, RangedUri rangeB, RangedUri expected) {
|
||||
RangedUri merged = rangeA.attemptMerge(rangeB);
|
||||
assertEquals(expected, merged);
|
||||
merged = rangeB.attemptMerge(rangeA);
|
||||
assertEquals(expected, merged);
|
||||
}
|
||||
|
||||
private void assertNonMerge(RangedUri rangeA, RangedUri rangeB) {
|
||||
RangedUri merged = rangeA.attemptMerge(rangeB);
|
||||
assertNull(merged);
|
||||
merged = rangeB.attemptMerge(rangeA);
|
||||
assertNull(merged);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,42 @@
|
|||
/*
|
||||
* 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.chunk.Format;
|
||||
import com.google.android.exoplayer.dash.mpd.SegmentBase.SingleSegmentBase;
|
||||
import com.google.android.exoplayer.util.MimeTypes;
|
||||
|
||||
import junit.framework.TestCase;
|
||||
|
||||
/**
|
||||
* Unit test for {@link Representation}.
|
||||
*/
|
||||
public class RepresentationTest extends TestCase {
|
||||
|
||||
public void testGetCacheKey() {
|
||||
String uri = "http://www.google.com";
|
||||
SegmentBase base = new SingleSegmentBase(new RangedUri(uri, null, 0, 1), 1, 0, uri, 1, 1);
|
||||
Format format = new Format("0", MimeTypes.VIDEO_MP4, 1920, 1080, 0, 0, 2500000);
|
||||
Representation representation = Representation.newInstance(-1, -1, "test_stream_1", 3,
|
||||
format, base);
|
||||
assertEquals("test_stream_1.0.3", representation.getCacheKey());
|
||||
|
||||
format = new Format("150", MimeTypes.VIDEO_MP4, 1920, 1080, 0, 0, 2500000);
|
||||
representation = Representation.newInstance(-1, -1, "test_stream_1", -1, format, base);
|
||||
assertEquals("test_stream_1.150.-1", representation.getCacheKey());
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,66 @@
|
|||
/*
|
||||
* 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 junit.framework.TestCase;
|
||||
|
||||
/**
|
||||
* Unit test for {@link UrlTemplate}.
|
||||
*/
|
||||
public class UrlTemplateTest extends TestCase {
|
||||
|
||||
public void testRealExamples() {
|
||||
String template = "QualityLevels($Bandwidth$)/Fragments(video=$Time$,format=mpd-time-csf)";
|
||||
UrlTemplate urlTemplate = UrlTemplate.compile(template);
|
||||
String url = urlTemplate.buildUri("abc1", 10, 650000, 5000);
|
||||
assertEquals("QualityLevels(650000)/Fragments(video=5000,format=mpd-time-csf)", url);
|
||||
|
||||
template = "$RepresentationID$/$Number$";
|
||||
urlTemplate = UrlTemplate.compile(template);
|
||||
url = urlTemplate.buildUri("abc1", 10, 650000, 5000);
|
||||
assertEquals("abc1/10", url);
|
||||
|
||||
template = "chunk_ctvideo_cfm4s_rid$RepresentationID$_cn$Number$_w2073857842_mpd.m4s";
|
||||
urlTemplate = UrlTemplate.compile(template);
|
||||
url = urlTemplate.buildUri("abc1", 10, 650000, 5000);
|
||||
assertEquals("chunk_ctvideo_cfm4s_ridabc1_cn10_w2073857842_mpd.m4s", url);
|
||||
}
|
||||
|
||||
public void testFull() {
|
||||
String template = "$Bandwidth$_a_$RepresentationID$_b_$Time$_c_$Number$";
|
||||
UrlTemplate urlTemplate = UrlTemplate.compile(template);
|
||||
String url = urlTemplate.buildUri("abc1", 10, 650000, 5000);
|
||||
assertEquals("650000_a_abc1_b_5000_c_10", url);
|
||||
}
|
||||
|
||||
public void testFullWithDollarEscaping() {
|
||||
String template = "$$$Bandwidth$$$_a$$_$RepresentationID$_b_$Time$_c_$Number$$$";
|
||||
UrlTemplate urlTemplate = UrlTemplate.compile(template);
|
||||
String url = urlTemplate.buildUri("abc1", 10, 650000, 5000);
|
||||
assertEquals("$650000$_a$_abc1_b_5000_c_10$", url);
|
||||
}
|
||||
|
||||
public void testInvalidSubstitution() {
|
||||
String template = "$IllegalId$";
|
||||
try {
|
||||
UrlTemplate.compile(template);
|
||||
assertTrue(false);
|
||||
} catch (IllegalArgumentException e) {
|
||||
// Expected.
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,108 @@
|
|||
/*
|
||||
* 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.hls;
|
||||
|
||||
import com.google.android.exoplayer.C;
|
||||
|
||||
import junit.framework.TestCase;
|
||||
|
||||
import java.io.ByteArrayInputStream;
|
||||
import java.io.IOException;
|
||||
import java.nio.charset.Charset;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* Test for {@link HlsMasterPlaylistParserTest}
|
||||
*/
|
||||
public class HlsMasterPlaylistParserTest extends TestCase {
|
||||
|
||||
public void testParseMasterPlaylist() {
|
||||
String playlistUrl = "https://example.com/test.m3u8";
|
||||
String playlistString = "#EXTM3U\n"
|
||||
+ "\n"
|
||||
+ "#EXT-X-STREAM-INF:BANDWIDTH=1280000,CODECS=\"mp4a.40.2,avc1.66.30\",RESOLUTION=304x128\n"
|
||||
+ "http://example.com/low.m3u8\n"
|
||||
+ "\n"
|
||||
+ "#EXT-X-STREAM-INF:BANDWIDTH=1280000,CODECS=\"mp4a.40.2 , avc1.66.30 \"\n"
|
||||
+ "http://example.com/spaces_in_codecs.m3u8\n"
|
||||
+ "\n"
|
||||
+ "#EXT-X-STREAM-INF:BANDWIDTH=2560000,RESOLUTION=384x160\n"
|
||||
+ "http://example.com/mid.m3u8\n"
|
||||
+ "\n"
|
||||
+ "#EXT-X-STREAM-INF:BANDWIDTH=7680000\n"
|
||||
+ "http://example.com/hi.m3u8\n"
|
||||
+ "\n"
|
||||
+ "#EXT-X-STREAM-INF:BANDWIDTH=65000,CODECS=\"mp4a.40.5\"\n"
|
||||
+ "http://example.com/audio-only.m3u8";
|
||||
ByteArrayInputStream inputStream = new ByteArrayInputStream(
|
||||
playlistString.getBytes(Charset.forName(C.UTF8_NAME)));
|
||||
try {
|
||||
HlsPlaylist playlist = new HlsPlaylistParser().parse(playlistUrl, inputStream);
|
||||
assertNotNull(playlist);
|
||||
assertEquals(HlsPlaylist.TYPE_MASTER, playlist.type);
|
||||
|
||||
HlsMasterPlaylist masterPlaylist = (HlsMasterPlaylist) playlist;
|
||||
|
||||
List<Variant> variants = masterPlaylist.variants;
|
||||
assertNotNull(variants);
|
||||
assertEquals(5, variants.size());
|
||||
|
||||
assertEquals(0, variants.get(0).index);
|
||||
assertEquals(1280000, variants.get(0).bandwidth);
|
||||
assertNotNull(variants.get(0).codecs);
|
||||
assertEquals(2, variants.get(0).codecs.length);
|
||||
assertEquals("mp4a.40.2", variants.get(0).codecs[0]);
|
||||
assertEquals("avc1.66.30", variants.get(0).codecs[1]);
|
||||
assertEquals(304, variants.get(0).width);
|
||||
assertEquals(128, variants.get(0).height);
|
||||
assertEquals("http://example.com/low.m3u8", variants.get(0).url);
|
||||
|
||||
assertEquals(1, variants.get(1).index);
|
||||
assertEquals(1280000, variants.get(1).bandwidth);
|
||||
assertNotNull(variants.get(1).codecs);
|
||||
assertEquals(2, variants.get(1).codecs.length);
|
||||
assertEquals("mp4a.40.2", variants.get(1).codecs[0]);
|
||||
assertEquals("avc1.66.30", variants.get(1).codecs[1]);
|
||||
assertEquals("http://example.com/spaces_in_codecs.m3u8", variants.get(1).url);
|
||||
|
||||
assertEquals(2, variants.get(2).index);
|
||||
assertEquals(2560000, variants.get(2).bandwidth);
|
||||
assertEquals(null, variants.get(2).codecs);
|
||||
assertEquals(384, variants.get(2).width);
|
||||
assertEquals(160, variants.get(2).height);
|
||||
assertEquals("http://example.com/mid.m3u8", variants.get(2).url);
|
||||
|
||||
assertEquals(3, variants.get(3).index);
|
||||
assertEquals(7680000, variants.get(3).bandwidth);
|
||||
assertEquals(null, variants.get(3).codecs);
|
||||
assertEquals(-1, variants.get(3).width);
|
||||
assertEquals(-1, variants.get(3).height);
|
||||
assertEquals("http://example.com/hi.m3u8", variants.get(3).url);
|
||||
|
||||
assertEquals(4, variants.get(4).index);
|
||||
assertEquals(65000, variants.get(4).bandwidth);
|
||||
assertNotNull(variants.get(4).codecs);
|
||||
assertEquals(1, variants.get(4).codecs.length);
|
||||
assertEquals("mp4a.40.5", variants.get(4).codecs[0]);
|
||||
assertEquals(-1, variants.get(4).width);
|
||||
assertEquals(-1, variants.get(4).height);
|
||||
assertEquals("http://example.com/audio-only.m3u8", variants.get(4).url);
|
||||
} catch (IOException exception) {
|
||||
fail(exception.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,135 @@
|
|||
/*
|
||||
* 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.hls;
|
||||
|
||||
import com.google.android.exoplayer.C;
|
||||
|
||||
import junit.framework.TestCase;
|
||||
|
||||
import java.io.ByteArrayInputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.nio.charset.Charset;
|
||||
import java.util.List;
|
||||
import java.util.Locale;
|
||||
|
||||
/**
|
||||
* Test for {@link HlsMediaPlaylistParserTest}
|
||||
*/
|
||||
public class HlsMediaPlaylistParserTest extends TestCase {
|
||||
|
||||
public void testParseMediaPlaylist() {
|
||||
String playlistUrl = "https://example.com/test.m3u8";
|
||||
String playlistString = "#EXTM3U\n"
|
||||
+ "#EXT-X-VERSION:3\n"
|
||||
+ "#EXT-X-TARGETDURATION:8\n"
|
||||
+ "#EXT-X-MEDIA-SEQUENCE:2679\n"
|
||||
+ "#EXT-X-ALLOW-CACHE:YES\n"
|
||||
+ "\n"
|
||||
+ "#EXTINF:7.975,\n"
|
||||
+ "#EXT-X-BYTERANGE:51370@0\n"
|
||||
+ "https://priv.example.com/fileSequence2679.ts\n"
|
||||
+ "\n"
|
||||
+ "#EXT-X-KEY:METHOD=AES-128,URI=\"https://priv.example.com/key.php?r=2680\",IV=0x1566B\n"
|
||||
+ "#EXTINF:7.975,\n"
|
||||
+ "#EXT-X-BYTERANGE:51501@51370\n"
|
||||
+ "https://priv.example.com/fileSequence2680.ts\n"
|
||||
+ "\n"
|
||||
+ "#EXT-X-KEY:METHOD=NONE\n"
|
||||
+ "#EXTINF:7.941,\n"
|
||||
+ "#EXT-X-BYTERANGE:51501\n" // @102871
|
||||
+ "https://priv.example.com/fileSequence2681.ts\n"
|
||||
+ "\n"
|
||||
+ "#EXT-X-DISCONTINUITY\n"
|
||||
+ "#EXT-X-KEY:METHOD=AES-128,URI=\"https://priv.example.com/key.php?r=2682\"\n"
|
||||
+ "#EXTINF:7.975,\n"
|
||||
+ "#EXT-X-BYTERANGE:51740\n" // @154372
|
||||
+ "https://priv.example.com/fileSequence2682.ts\n"
|
||||
+ "\n"
|
||||
+ "#EXTINF:7.975,\n"
|
||||
+ "https://priv.example.com/fileSequence2683.ts\n"
|
||||
+ "#EXT-X-ENDLIST";
|
||||
InputStream inputStream = new ByteArrayInputStream(
|
||||
playlistString.getBytes(Charset.forName(C.UTF8_NAME)));
|
||||
try {
|
||||
HlsPlaylist playlist = new HlsPlaylistParser().parse(playlistUrl, inputStream);
|
||||
assertNotNull(playlist);
|
||||
assertEquals(HlsPlaylist.TYPE_MEDIA, playlist.type);
|
||||
|
||||
HlsMediaPlaylist mediaPlaylist = (HlsMediaPlaylist) playlist;
|
||||
|
||||
assertEquals(2679, mediaPlaylist.mediaSequence);
|
||||
assertEquals(8, mediaPlaylist.targetDurationSecs);
|
||||
assertEquals(3, mediaPlaylist.version);
|
||||
assertEquals(false, mediaPlaylist.live);
|
||||
List<HlsMediaPlaylist.Segment> segments = mediaPlaylist.segments;
|
||||
assertNotNull(segments);
|
||||
assertEquals(5, segments.size());
|
||||
|
||||
assertEquals(false, segments.get(0).discontinuity);
|
||||
assertEquals(7.975, segments.get(0).durationSecs);
|
||||
assertEquals(null, segments.get(0).encryptionMethod);
|
||||
assertEquals(null, segments.get(0).encryptionKeyUri);
|
||||
assertEquals(null, segments.get(0).encryptionIV);
|
||||
assertEquals(51370, segments.get(0).byterangeLength);
|
||||
assertEquals(0, segments.get(0).byterangeOffset);
|
||||
assertEquals("https://priv.example.com/fileSequence2679.ts", segments.get(0).url);
|
||||
|
||||
assertEquals(false, segments.get(1).discontinuity);
|
||||
assertEquals(7.975, segments.get(1).durationSecs);
|
||||
assertEquals("AES-128", segments.get(1).encryptionMethod);
|
||||
assertEquals("https://priv.example.com/key.php?r=2680", segments.get(1).encryptionKeyUri);
|
||||
assertEquals("0x1566B", segments.get(1).encryptionIV);
|
||||
assertEquals(51501, segments.get(1).byterangeLength);
|
||||
assertEquals(51370, segments.get(1).byterangeOffset);
|
||||
assertEquals("https://priv.example.com/fileSequence2680.ts", segments.get(1).url);
|
||||
|
||||
assertEquals(false, segments.get(2).discontinuity);
|
||||
assertEquals(7.941, segments.get(2).durationSecs);
|
||||
assertEquals(HlsMediaPlaylist.ENCRYPTION_METHOD_NONE, segments.get(2).encryptionMethod);
|
||||
assertEquals(null, segments.get(2).encryptionKeyUri);
|
||||
assertEquals(null, segments.get(2).encryptionIV);
|
||||
assertEquals(51501, segments.get(2).byterangeLength);
|
||||
assertEquals(102871, segments.get(2).byterangeOffset);
|
||||
assertEquals("https://priv.example.com/fileSequence2681.ts", segments.get(2).url);
|
||||
|
||||
assertEquals(true, segments.get(3).discontinuity);
|
||||
assertEquals(7.975, segments.get(3).durationSecs);
|
||||
assertEquals("AES-128", segments.get(3).encryptionMethod);
|
||||
assertEquals("https://priv.example.com/key.php?r=2682", segments.get(3).encryptionKeyUri);
|
||||
// 0xA7A == 2682.
|
||||
assertNotNull(segments.get(3).encryptionIV);
|
||||
assertEquals("A7A", segments.get(3).encryptionIV.toUpperCase(Locale.getDefault()));
|
||||
assertEquals(51740, segments.get(3).byterangeLength);
|
||||
assertEquals(154372, segments.get(3).byterangeOffset);
|
||||
assertEquals("https://priv.example.com/fileSequence2682.ts", segments.get(3).url);
|
||||
|
||||
assertEquals(false, segments.get(4).discontinuity);
|
||||
assertEquals(7.975, segments.get(4).durationSecs);
|
||||
assertEquals("AES-128", segments.get(4).encryptionMethod);
|
||||
assertEquals("https://priv.example.com/key.php?r=2682", segments.get(4).encryptionKeyUri);
|
||||
// 0xA7A == 2682.
|
||||
assertNotNull(segments.get(4).encryptionIV);
|
||||
assertEquals("A7A", segments.get(4).encryptionIV.toUpperCase(Locale.getDefault()));
|
||||
assertEquals(C.LENGTH_UNBOUNDED, segments.get(4).byterangeLength);
|
||||
assertEquals(0, segments.get(4).byterangeOffset);
|
||||
assertEquals("https://priv.example.com/fileSequence2683.ts", segments.get(4).url);
|
||||
} catch (IOException exception) {
|
||||
fail(exception.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,46 @@
|
|||
/*
|
||||
* 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.metadata;
|
||||
|
||||
import junit.framework.TestCase;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* Test for {@link Id3Parser}
|
||||
*/
|
||||
public class Id3ParserTest extends TestCase {
|
||||
|
||||
public void testParseTxxxFrames() {
|
||||
byte[] rawId3 = new byte[] { 73, 68, 51, 4, 0, 0, 0, 0, 0, 41, 84, 88, 88, 88, 0, 0, 0, 31,
|
||||
0, 0, 3, 0, 109, 100, 105, 97, 108, 111, 103, 95, 86, 73, 78, 68, 73, 67, 79, 49, 53, 50,
|
||||
55, 54, 54, 52, 95, 115, 116, 97, 114, 116, 0 };
|
||||
|
||||
Id3Parser parser = new Id3Parser();
|
||||
try {
|
||||
Map<String, Object> metadata = parser.parse(rawId3, rawId3.length);
|
||||
assertNotNull(metadata);
|
||||
assertEquals(1, metadata.size());
|
||||
TxxxMetadata txxx = (TxxxMetadata) metadata.get(TxxxMetadata.TYPE);
|
||||
assertNotNull(txxx);
|
||||
assertEquals("", txxx.description);
|
||||
assertEquals("mdialog_VINDICO1527664_start", txxx.value);
|
||||
} catch (Exception exception) {
|
||||
fail(exception.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,133 @@
|
|||
/*
|
||||
* 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.mp4;
|
||||
|
||||
import junit.framework.TestCase;
|
||||
|
||||
import java.util.Arrays;
|
||||
|
||||
/**
|
||||
* Tests for {@link Mp4Util}.
|
||||
*/
|
||||
public class Mp4UtilTest extends TestCase {
|
||||
|
||||
private static final int TEST_PARTIAL_NAL_POSITION = 4;
|
||||
private static final int TEST_NAL_POSITION = 10;
|
||||
|
||||
public void testFindNalUnit() {
|
||||
byte[] data = buildTestData();
|
||||
|
||||
// Should find NAL unit.
|
||||
int result = Mp4Util.findNalUnit(data, 0, data.length);
|
||||
assertEquals(TEST_NAL_POSITION, result);
|
||||
// Should find NAL unit whose prefix ends one byte before the limit.
|
||||
result = Mp4Util.findNalUnit(data, 0, TEST_NAL_POSITION + 4);
|
||||
assertEquals(TEST_NAL_POSITION, result);
|
||||
// Shouldn't find NAL unit whose prefix ends at the limit (since the limit is exclusive).
|
||||
result = Mp4Util.findNalUnit(data, 0, TEST_NAL_POSITION + 3);
|
||||
assertEquals(TEST_NAL_POSITION + 3, result);
|
||||
// Should find NAL unit whose prefix starts at the offset.
|
||||
result = Mp4Util.findNalUnit(data, TEST_NAL_POSITION, data.length);
|
||||
assertEquals(TEST_NAL_POSITION, result);
|
||||
// Shouldn't find NAL unit whose prefix starts one byte past the offset.
|
||||
result = Mp4Util.findNalUnit(data, TEST_NAL_POSITION + 1, data.length);
|
||||
assertEquals(data.length, result);
|
||||
}
|
||||
|
||||
public void testFindNalUnitWithPrefix() {
|
||||
byte[] data = buildTestData();
|
||||
|
||||
// First byte of NAL unit in data1, rest in data2.
|
||||
boolean[] prefixFlags = new boolean[3];
|
||||
byte[] data1 = Arrays.copyOfRange(data, 0, TEST_NAL_POSITION + 1);
|
||||
byte[] data2 = Arrays.copyOfRange(data, TEST_NAL_POSITION + 1, data.length);
|
||||
int result = Mp4Util.findNalUnit(data1, 0, data1.length, prefixFlags);
|
||||
assertEquals(data1.length, result);
|
||||
result = Mp4Util.findNalUnit(data2, 0, data2.length, prefixFlags);
|
||||
assertEquals(-1, result);
|
||||
assertPrefixFlagsCleared(prefixFlags);
|
||||
|
||||
// First three bytes of NAL unit in data1, rest in data2.
|
||||
prefixFlags = new boolean[3];
|
||||
data1 = Arrays.copyOfRange(data, 0, TEST_NAL_POSITION + 3);
|
||||
data2 = Arrays.copyOfRange(data, TEST_NAL_POSITION + 3, data.length);
|
||||
result = Mp4Util.findNalUnit(data1, 0, data1.length, prefixFlags);
|
||||
assertEquals(data1.length, result);
|
||||
result = Mp4Util.findNalUnit(data2, 0, data2.length, prefixFlags);
|
||||
assertEquals(-3, result);
|
||||
assertPrefixFlagsCleared(prefixFlags);
|
||||
|
||||
// First byte of NAL unit in data1, second byte in data2, rest in data3.
|
||||
prefixFlags = new boolean[3];
|
||||
data1 = Arrays.copyOfRange(data, 0, TEST_NAL_POSITION + 1);
|
||||
data2 = Arrays.copyOfRange(data, TEST_NAL_POSITION + 1, TEST_NAL_POSITION + 2);
|
||||
byte[] data3 = Arrays.copyOfRange(data, TEST_NAL_POSITION + 2, data.length);
|
||||
result = Mp4Util.findNalUnit(data1, 0, data1.length, prefixFlags);
|
||||
assertEquals(data1.length, result);
|
||||
result = Mp4Util.findNalUnit(data2, 0, data2.length, prefixFlags);
|
||||
assertEquals(data2.length, result);
|
||||
result = Mp4Util.findNalUnit(data3, 0, data3.length, prefixFlags);
|
||||
assertEquals(-2, result);
|
||||
assertPrefixFlagsCleared(prefixFlags);
|
||||
|
||||
// NAL unit split with one byte in four arrays.
|
||||
prefixFlags = new boolean[3];
|
||||
data1 = Arrays.copyOfRange(data, 0, TEST_NAL_POSITION + 1);
|
||||
data2 = Arrays.copyOfRange(data, TEST_NAL_POSITION + 1, TEST_NAL_POSITION + 2);
|
||||
data3 = Arrays.copyOfRange(data, TEST_NAL_POSITION + 2, TEST_NAL_POSITION + 3);
|
||||
byte[] data4 = Arrays.copyOfRange(data, TEST_NAL_POSITION + 2, data.length);
|
||||
result = Mp4Util.findNalUnit(data1, 0, data1.length, prefixFlags);
|
||||
assertEquals(data1.length, result);
|
||||
result = Mp4Util.findNalUnit(data2, 0, data2.length, prefixFlags);
|
||||
assertEquals(data2.length, result);
|
||||
result = Mp4Util.findNalUnit(data3, 0, data3.length, prefixFlags);
|
||||
assertEquals(data3.length, result);
|
||||
result = Mp4Util.findNalUnit(data4, 0, data4.length, prefixFlags);
|
||||
assertEquals(-3, result);
|
||||
assertPrefixFlagsCleared(prefixFlags);
|
||||
|
||||
// NAL unit entirely in data2. data1 ends with partial prefix.
|
||||
prefixFlags = new boolean[3];
|
||||
data1 = Arrays.copyOfRange(data, 0, TEST_PARTIAL_NAL_POSITION + 2);
|
||||
data2 = Arrays.copyOfRange(data, TEST_PARTIAL_NAL_POSITION + 2, data.length);
|
||||
result = Mp4Util.findNalUnit(data1, 0, data1.length, prefixFlags);
|
||||
assertEquals(data1.length, result);
|
||||
result = Mp4Util.findNalUnit(data2, 0, data2.length, prefixFlags);
|
||||
assertEquals(4, result);
|
||||
assertPrefixFlagsCleared(prefixFlags);
|
||||
}
|
||||
|
||||
private static byte[] buildTestData() {
|
||||
byte[] data = new byte[20];
|
||||
for (int i = 0; i < data.length; i++) {
|
||||
data[i] = (byte) 0xFF;
|
||||
}
|
||||
// Insert an incomplete NAL unit start code.
|
||||
data[TEST_PARTIAL_NAL_POSITION] = 0;
|
||||
data[TEST_PARTIAL_NAL_POSITION + 1] = 0;
|
||||
// Insert a complete NAL unit start code.
|
||||
data[TEST_NAL_POSITION] = 0;
|
||||
data[TEST_NAL_POSITION + 1] = 0;
|
||||
data[TEST_NAL_POSITION + 2] = 1;
|
||||
data[TEST_NAL_POSITION + 3] = 5;
|
||||
return data;
|
||||
}
|
||||
|
||||
private static void assertPrefixFlagsCleared(boolean[] flags) {
|
||||
assertEquals(false, flags[0] || flags[1] || flags[2]);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,38 @@
|
|||
/*
|
||||
* 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.testutil;
|
||||
|
||||
import java.util.Random;
|
||||
|
||||
/**
|
||||
* Utility methods for tests.
|
||||
*/
|
||||
public class Util {
|
||||
|
||||
private Util() {}
|
||||
|
||||
public static byte[] buildTestData(int length) {
|
||||
return buildTestData(length, length);
|
||||
}
|
||||
|
||||
public static byte[] buildTestData(int length, int seed) {
|
||||
Random random = new Random(seed);
|
||||
byte[] source = new byte[length];
|
||||
random.nextBytes(source);
|
||||
return source;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,134 @@
|
|||
/*
|
||||
* 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.text.webvtt;
|
||||
|
||||
import com.google.android.exoplayer.C;
|
||||
|
||||
import android.test.InstrumentationTestCase;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
|
||||
/**
|
||||
* Unit test for {@link WebvttParser}.
|
||||
*/
|
||||
public class WebvttParserTest extends InstrumentationTestCase {
|
||||
|
||||
private static final String TYPICAL_WEBVTT_FILE = "webvtt/typical";
|
||||
private static final String TYPICAL_WITH_IDS_WEBVTT_FILE = "webvtt/typical_with_identifiers";
|
||||
private static final String TYPICAL_WITH_TAGS_WEBVTT_FILE = "webvtt/typical_with_tags";
|
||||
private static final String EMPTY_WEBVTT_FILE = "webvtt/empty";
|
||||
|
||||
public void testParseNullWebvttFile() throws IOException {
|
||||
WebvttParser parser = new WebvttParser();
|
||||
InputStream inputStream =
|
||||
getInstrumentation().getContext().getResources().getAssets().open(EMPTY_WEBVTT_FILE);
|
||||
|
||||
try {
|
||||
parser.parse(inputStream, C.UTF8_NAME, 0);
|
||||
fail("Expected IOException");
|
||||
} catch (IOException expected) {
|
||||
// Do nothing.
|
||||
}
|
||||
}
|
||||
|
||||
public void testParseTypicalWebvttFile() throws IOException {
|
||||
WebvttParser parser = new WebvttParser();
|
||||
InputStream inputStream =
|
||||
getInstrumentation().getContext().getResources().getAssets().open(TYPICAL_WEBVTT_FILE);
|
||||
WebvttSubtitle subtitle = parser.parse(inputStream, C.UTF8_NAME, 0);
|
||||
|
||||
// test start time and event count
|
||||
long startTimeUs = 5000000;
|
||||
assertEquals(startTimeUs, subtitle.getStartTime());
|
||||
assertEquals(4, subtitle.getEventTimeCount());
|
||||
|
||||
// test first cue
|
||||
assertEquals(startTimeUs, subtitle.getEventTime(0));
|
||||
assertEquals("This is the first subtitle.",
|
||||
subtitle.getText(subtitle.getEventTime(0)));
|
||||
assertEquals(startTimeUs + 1234000, subtitle.getEventTime(1));
|
||||
|
||||
// test second cue
|
||||
assertEquals(startTimeUs + 2345000, subtitle.getEventTime(2));
|
||||
assertEquals("This is the second subtitle.",
|
||||
subtitle.getText(subtitle.getEventTime(2)));
|
||||
assertEquals(startTimeUs + 3456000, subtitle.getEventTime(3));
|
||||
}
|
||||
|
||||
public void testParseTypicalWithIdsWebvttFile() throws IOException {
|
||||
WebvttParser parser = new WebvttParser();
|
||||
InputStream inputStream =
|
||||
getInstrumentation().getContext().getResources().getAssets()
|
||||
.open(TYPICAL_WITH_IDS_WEBVTT_FILE);
|
||||
WebvttSubtitle subtitle = parser.parse(inputStream, C.UTF8_NAME, 0);
|
||||
|
||||
// test start time and event count
|
||||
long startTimeUs = 5000000;
|
||||
assertEquals(startTimeUs, subtitle.getStartTime());
|
||||
assertEquals(4, subtitle.getEventTimeCount());
|
||||
|
||||
// test first cue
|
||||
assertEquals(startTimeUs, subtitle.getEventTime(0));
|
||||
assertEquals("This is the first subtitle.",
|
||||
subtitle.getText(subtitle.getEventTime(0)));
|
||||
assertEquals(startTimeUs + 1234000, subtitle.getEventTime(1));
|
||||
|
||||
// test second cue
|
||||
assertEquals(startTimeUs + 2345000, subtitle.getEventTime(2));
|
||||
assertEquals("This is the second subtitle.",
|
||||
subtitle.getText(subtitle.getEventTime(2)));
|
||||
assertEquals(startTimeUs + 3456000, subtitle.getEventTime(3));
|
||||
}
|
||||
|
||||
public void testParseTypicalWithTagsWebvttFile() throws IOException {
|
||||
WebvttParser parser = new WebvttParser();
|
||||
InputStream inputStream =
|
||||
getInstrumentation().getContext().getResources().getAssets()
|
||||
.open(TYPICAL_WITH_TAGS_WEBVTT_FILE);
|
||||
WebvttSubtitle subtitle = parser.parse(inputStream, C.UTF8_NAME, 0);
|
||||
|
||||
// test start time and event count
|
||||
long startTimeUs = 5000000;
|
||||
assertEquals(startTimeUs, subtitle.getStartTime());
|
||||
assertEquals(8, subtitle.getEventTimeCount());
|
||||
|
||||
// test first cue
|
||||
assertEquals(startTimeUs, subtitle.getEventTime(0));
|
||||
assertEquals("This is the first subtitle.",
|
||||
subtitle.getText(subtitle.getEventTime(0)));
|
||||
assertEquals(startTimeUs + 1234000, subtitle.getEventTime(1));
|
||||
|
||||
// test second cue
|
||||
assertEquals(startTimeUs + 2345000, subtitle.getEventTime(2));
|
||||
assertEquals("This is the second subtitle.",
|
||||
subtitle.getText(subtitle.getEventTime(2)));
|
||||
assertEquals(startTimeUs + 3456000, subtitle.getEventTime(3));
|
||||
|
||||
// test third cue
|
||||
assertEquals(startTimeUs + 4000000, subtitle.getEventTime(4));
|
||||
assertEquals("This is the third subtitle.",
|
||||
subtitle.getText(subtitle.getEventTime(4)));
|
||||
assertEquals(startTimeUs + 5000000, subtitle.getEventTime(5));
|
||||
|
||||
// test fourth cue
|
||||
assertEquals(startTimeUs + 6000000, subtitle.getEventTime(6));
|
||||
assertEquals("This is the <fourth> &subtitle.",
|
||||
subtitle.getText(subtitle.getEventTime(6)));
|
||||
assertEquals(startTimeUs + 7000000, subtitle.getEventTime(7));
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,204 @@
|
|||
/*
|
||||
* 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.text.webvtt;
|
||||
|
||||
import junit.framework.TestCase;
|
||||
|
||||
/**
|
||||
* Unit test for {@link WebvttSubtitle}.
|
||||
*/
|
||||
public class WebvttSubtitleTest extends TestCase {
|
||||
|
||||
private static final String FIRST_SUBTITLE_STRING = "This is the first subtitle.";
|
||||
private static final String SECOND_SUBTITLE_STRING = "This is the second subtitle.";
|
||||
private static final String FIRST_AND_SECOND_SUBTITLE_STRING =
|
||||
FIRST_SUBTITLE_STRING + SECOND_SUBTITLE_STRING;
|
||||
|
||||
private WebvttSubtitle emptySubtitle = new WebvttSubtitle(new String[] {}, 0, new long[] {});
|
||||
|
||||
private WebvttSubtitle simpleSubtitle = new WebvttSubtitle(
|
||||
new String[] {FIRST_SUBTITLE_STRING, SECOND_SUBTITLE_STRING}, 0,
|
||||
new long[] {1000000, 2000000, 3000000, 4000000});
|
||||
|
||||
private WebvttSubtitle overlappingSubtitle = new WebvttSubtitle(
|
||||
new String[] {FIRST_SUBTITLE_STRING, SECOND_SUBTITLE_STRING}, 0,
|
||||
new long[] {1000000, 3000000, 2000000, 4000000});
|
||||
|
||||
private WebvttSubtitle nestedSubtitle = new WebvttSubtitle(
|
||||
new String[] {FIRST_SUBTITLE_STRING, SECOND_SUBTITLE_STRING}, 0,
|
||||
new long[] {1000000, 4000000, 2000000, 3000000});
|
||||
|
||||
public void testEventCount() {
|
||||
assertEquals(0, emptySubtitle.getEventTimeCount());
|
||||
assertEquals(4, simpleSubtitle.getEventTimeCount());
|
||||
assertEquals(4, overlappingSubtitle.getEventTimeCount());
|
||||
assertEquals(4, nestedSubtitle.getEventTimeCount());
|
||||
}
|
||||
|
||||
public void testStartTime() {
|
||||
assertEquals(0, emptySubtitle.getStartTime());
|
||||
assertEquals(0, simpleSubtitle.getStartTime());
|
||||
assertEquals(0, overlappingSubtitle.getStartTime());
|
||||
assertEquals(0, nestedSubtitle.getStartTime());
|
||||
}
|
||||
|
||||
public void testLastEventTime() {
|
||||
assertEquals(-1, emptySubtitle.getLastEventTime());
|
||||
assertEquals(4000000, simpleSubtitle.getLastEventTime());
|
||||
assertEquals(4000000, overlappingSubtitle.getLastEventTime());
|
||||
assertEquals(4000000, nestedSubtitle.getLastEventTime());
|
||||
}
|
||||
|
||||
public void testSimpleSubtitleEventTimes() {
|
||||
testSubtitleEventTimesHelper(simpleSubtitle);
|
||||
}
|
||||
|
||||
public void testSimpleSubtitleEventIndices() {
|
||||
testSubtitleEventIndicesHelper(simpleSubtitle);
|
||||
}
|
||||
|
||||
public void testSimpleSubtitleText() {
|
||||
// Test before first subtitle
|
||||
assertNull(simpleSubtitle.getText(0));
|
||||
assertNull(simpleSubtitle.getText(500000));
|
||||
assertNull(simpleSubtitle.getText(999999));
|
||||
|
||||
// Test first subtitle
|
||||
assertEquals(FIRST_SUBTITLE_STRING, simpleSubtitle.getText(1000000));
|
||||
assertEquals(FIRST_SUBTITLE_STRING, simpleSubtitle.getText(1500000));
|
||||
assertEquals(FIRST_SUBTITLE_STRING, simpleSubtitle.getText(1999999));
|
||||
|
||||
// Test after first subtitle, before second subtitle
|
||||
assertNull(simpleSubtitle.getText(2000000));
|
||||
assertNull(simpleSubtitle.getText(2500000));
|
||||
assertNull(simpleSubtitle.getText(2999999));
|
||||
|
||||
// Test second subtitle
|
||||
assertEquals(SECOND_SUBTITLE_STRING, simpleSubtitle.getText(3000000));
|
||||
assertEquals(SECOND_SUBTITLE_STRING, simpleSubtitle.getText(3500000));
|
||||
assertEquals(SECOND_SUBTITLE_STRING, simpleSubtitle.getText(3999999));
|
||||
|
||||
// Test after second subtitle
|
||||
assertNull(simpleSubtitle.getText(4000000));
|
||||
assertNull(simpleSubtitle.getText(4500000));
|
||||
assertNull(simpleSubtitle.getText(Long.MAX_VALUE));
|
||||
}
|
||||
|
||||
public void testOverlappingSubtitleEventTimes() {
|
||||
testSubtitleEventTimesHelper(overlappingSubtitle);
|
||||
}
|
||||
|
||||
public void testOverlappingSubtitleEventIndices() {
|
||||
testSubtitleEventIndicesHelper(overlappingSubtitle);
|
||||
}
|
||||
|
||||
public void testOverlappingSubtitleText() {
|
||||
// Test before first subtitle
|
||||
assertNull(overlappingSubtitle.getText(0));
|
||||
assertNull(overlappingSubtitle.getText(500000));
|
||||
assertNull(overlappingSubtitle.getText(999999));
|
||||
|
||||
// Test first subtitle
|
||||
assertEquals(FIRST_SUBTITLE_STRING, overlappingSubtitle.getText(1000000));
|
||||
assertEquals(FIRST_SUBTITLE_STRING, overlappingSubtitle.getText(1500000));
|
||||
assertEquals(FIRST_SUBTITLE_STRING, overlappingSubtitle.getText(1999999));
|
||||
|
||||
// Test after first and second subtitle
|
||||
assertEquals(FIRST_AND_SECOND_SUBTITLE_STRING, overlappingSubtitle.getText(2000000));
|
||||
assertEquals(FIRST_AND_SECOND_SUBTITLE_STRING, overlappingSubtitle.getText(2500000));
|
||||
assertEquals(FIRST_AND_SECOND_SUBTITLE_STRING, overlappingSubtitle.getText(2999999));
|
||||
|
||||
// Test second subtitle
|
||||
assertEquals(SECOND_SUBTITLE_STRING, overlappingSubtitle.getText(3000000));
|
||||
assertEquals(SECOND_SUBTITLE_STRING, overlappingSubtitle.getText(3500000));
|
||||
assertEquals(SECOND_SUBTITLE_STRING, overlappingSubtitle.getText(3999999));
|
||||
|
||||
// Test after second subtitle
|
||||
assertNull(overlappingSubtitle.getText(4000000));
|
||||
assertNull(overlappingSubtitle.getText(4500000));
|
||||
assertNull(overlappingSubtitle.getText(Long.MAX_VALUE));
|
||||
}
|
||||
|
||||
public void testNestedSubtitleEventTimes() {
|
||||
testSubtitleEventTimesHelper(nestedSubtitle);
|
||||
}
|
||||
|
||||
public void testNestedSubtitleEventIndices() {
|
||||
testSubtitleEventIndicesHelper(nestedSubtitle);
|
||||
}
|
||||
|
||||
public void testNestedSubtitleText() {
|
||||
// Test before first subtitle
|
||||
assertNull(nestedSubtitle.getText(0));
|
||||
assertNull(nestedSubtitle.getText(500000));
|
||||
assertNull(nestedSubtitle.getText(999999));
|
||||
|
||||
// Test first subtitle
|
||||
assertEquals(FIRST_SUBTITLE_STRING, nestedSubtitle.getText(1000000));
|
||||
assertEquals(FIRST_SUBTITLE_STRING, nestedSubtitle.getText(1500000));
|
||||
assertEquals(FIRST_SUBTITLE_STRING, nestedSubtitle.getText(1999999));
|
||||
|
||||
// Test after first and second subtitle
|
||||
assertEquals(FIRST_AND_SECOND_SUBTITLE_STRING, nestedSubtitle.getText(2000000));
|
||||
assertEquals(FIRST_AND_SECOND_SUBTITLE_STRING, nestedSubtitle.getText(2500000));
|
||||
assertEquals(FIRST_AND_SECOND_SUBTITLE_STRING, nestedSubtitle.getText(2999999));
|
||||
|
||||
// Test first subtitle
|
||||
assertEquals(FIRST_SUBTITLE_STRING, nestedSubtitle.getText(3000000));
|
||||
assertEquals(FIRST_SUBTITLE_STRING, nestedSubtitle.getText(3500000));
|
||||
assertEquals(FIRST_SUBTITLE_STRING, nestedSubtitle.getText(3999999));
|
||||
|
||||
// Test after second subtitle
|
||||
assertNull(nestedSubtitle.getText(4000000));
|
||||
assertNull(nestedSubtitle.getText(4500000));
|
||||
assertNull(nestedSubtitle.getText(Long.MAX_VALUE));
|
||||
}
|
||||
|
||||
private void testSubtitleEventTimesHelper(WebvttSubtitle subtitle) {
|
||||
assertEquals(1000000, subtitle.getEventTime(0));
|
||||
assertEquals(2000000, subtitle.getEventTime(1));
|
||||
assertEquals(3000000, subtitle.getEventTime(2));
|
||||
assertEquals(4000000, subtitle.getEventTime(3));
|
||||
}
|
||||
|
||||
private void testSubtitleEventIndicesHelper(WebvttSubtitle subtitle) {
|
||||
// Test first event
|
||||
assertEquals(0, subtitle.getNextEventTimeIndex(0));
|
||||
assertEquals(0, subtitle.getNextEventTimeIndex(500000));
|
||||
assertEquals(0, subtitle.getNextEventTimeIndex(999999));
|
||||
|
||||
// Test second event
|
||||
assertEquals(1, subtitle.getNextEventTimeIndex(1000000));
|
||||
assertEquals(1, subtitle.getNextEventTimeIndex(1500000));
|
||||
assertEquals(1, subtitle.getNextEventTimeIndex(1999999));
|
||||
|
||||
// Test third event
|
||||
assertEquals(2, subtitle.getNextEventTimeIndex(2000000));
|
||||
assertEquals(2, subtitle.getNextEventTimeIndex(2500000));
|
||||
assertEquals(2, subtitle.getNextEventTimeIndex(2999999));
|
||||
|
||||
// Test fourth event
|
||||
assertEquals(3, subtitle.getNextEventTimeIndex(3000000));
|
||||
assertEquals(3, subtitle.getNextEventTimeIndex(3500000));
|
||||
assertEquals(3, subtitle.getNextEventTimeIndex(3999999));
|
||||
|
||||
// Test null event (i.e. look for events after the last event)
|
||||
assertEquals(-1, subtitle.getNextEventTimeIndex(4000000));
|
||||
assertEquals(-1, subtitle.getNextEventTimeIndex(4500000));
|
||||
assertEquals(-1, subtitle.getNextEventTimeIndex(Long.MAX_VALUE));
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,151 @@
|
|||
/*
|
||||
* 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.upstream;
|
||||
|
||||
import com.google.android.exoplayer.C;
|
||||
|
||||
import junit.framework.TestCase;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
/**
|
||||
* Unit tests for {@link ByteArrayDataSource}.
|
||||
*/
|
||||
public class ByteArrayDataSourceTest extends TestCase {
|
||||
|
||||
private static final byte[] TEST_DATA = new byte[] {0, 1, 2, 3, 4, 5, 6, 7, 8, 9};
|
||||
private static final byte[] TEST_DATA_ODD = new byte[] {0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10};
|
||||
|
||||
public void testFullReadSingleBytes() {
|
||||
readTestData(TEST_DATA, 0, C.LENGTH_UNBOUNDED, 1, 0, 1, false);
|
||||
}
|
||||
|
||||
public void testFullReadAllBytes() {
|
||||
readTestData(TEST_DATA, 0, C.LENGTH_UNBOUNDED, 100, 0, 100, false);
|
||||
}
|
||||
|
||||
public void testLimitReadSingleBytes() {
|
||||
// Limit set to the length of the data.
|
||||
readTestData(TEST_DATA, 0, TEST_DATA.length, 1, 0, 1, false);
|
||||
// And less.
|
||||
readTestData(TEST_DATA, 0, 6, 1, 0, 1, false);
|
||||
}
|
||||
|
||||
public void testFullReadTwoBytes() {
|
||||
// Try with the total data length an exact multiple of the size of each individual read.
|
||||
readTestData(TEST_DATA, 0, C.LENGTH_UNBOUNDED, 2, 0, 2, false);
|
||||
// And not.
|
||||
readTestData(TEST_DATA_ODD, 0, C.LENGTH_UNBOUNDED, 2, 0, 2, false);
|
||||
}
|
||||
|
||||
public void testLimitReadTwoBytes() {
|
||||
// Try with the limit an exact multiple of the size of each individual read.
|
||||
readTestData(TEST_DATA, 0, 6, 2, 0, 2, false);
|
||||
// And not.
|
||||
readTestData(TEST_DATA, 0, 7, 2, 0, 2, false);
|
||||
}
|
||||
|
||||
public void testReadFromValidOffsets() {
|
||||
// Read from an offset without bound.
|
||||
readTestData(TEST_DATA, 1, C.LENGTH_UNBOUNDED, 1, 0, 1, false);
|
||||
// And with bound.
|
||||
readTestData(TEST_DATA, 1, 6, 1, 0, 1, false);
|
||||
// Read from the last possible offset without bound.
|
||||
readTestData(TEST_DATA, TEST_DATA.length - 1, C.LENGTH_UNBOUNDED, 1, 0, 1, false);
|
||||
// And with bound.
|
||||
readTestData(TEST_DATA, TEST_DATA.length - 1, 1, 1, 0, 1, false);
|
||||
}
|
||||
|
||||
public void testReadFromInvalidOffsets() {
|
||||
// Read from first invalid offset and check failure without bound.
|
||||
readTestData(TEST_DATA, TEST_DATA.length, C.LENGTH_UNBOUNDED, 1, 0, 1, true);
|
||||
// And with bound.
|
||||
readTestData(TEST_DATA, TEST_DATA.length, 1, 1, 0, 1, true);
|
||||
}
|
||||
|
||||
public void testReadWithInvalidLength() {
|
||||
// Read more data than is available.
|
||||
readTestData(TEST_DATA, 0, TEST_DATA.length + 1, 1, 0, 1, true);
|
||||
// And with bound.
|
||||
readTestData(TEST_DATA, 1, TEST_DATA.length, 1, 0, 1, true);
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests reading from a {@link ByteArrayDataSource} with various parameters.
|
||||
*
|
||||
* @param testData The data that the {@link ByteArrayDataSource} will wrap.
|
||||
* @param dataOffset The offset from which to read data.
|
||||
* @param dataLength The total length of data to read.
|
||||
* @param outputBufferLength The length of the target buffer for each read.
|
||||
* @param writeOffset The offset into {@code outputBufferLength} for each read.
|
||||
* @param maxReadLength The maximum length of each read.
|
||||
* @param expectFailOnOpen Whether it is expected that opening the source will fail.
|
||||
*/
|
||||
private void readTestData(byte[] testData, int dataOffset, int dataLength, int outputBufferLength,
|
||||
int writeOffset, int maxReadLength, boolean expectFailOnOpen) {
|
||||
int expectedFinalBytesRead =
|
||||
dataLength == C.LENGTH_UNBOUNDED ? (testData.length - dataOffset) : dataLength;
|
||||
ByteArrayDataSource dataSource = new ByteArrayDataSource(testData);
|
||||
boolean opened = false;
|
||||
try {
|
||||
// Open the source.
|
||||
long length = dataSource.open(new DataSpec(null, dataOffset, dataLength, null));
|
||||
opened = true;
|
||||
assertFalse(expectFailOnOpen);
|
||||
|
||||
// Verify the resolved length is as we expect.
|
||||
assertEquals(expectedFinalBytesRead, length);
|
||||
|
||||
byte[] outputBuffer = new byte[outputBufferLength];
|
||||
int accumulatedBytesRead = 0;
|
||||
while (true) {
|
||||
// Calculate a valid length for the next read, constraining by the specified output buffer
|
||||
// length, write offset and maximum write length input parameters.
|
||||
int requestedReadLength = Math.min(maxReadLength, outputBufferLength - writeOffset);
|
||||
assertTrue(requestedReadLength > 0);
|
||||
|
||||
int bytesRead = dataSource.read(outputBuffer, writeOffset, requestedReadLength);
|
||||
if (bytesRead != -1) {
|
||||
assertTrue(bytesRead > 0);
|
||||
assertTrue(bytesRead <= requestedReadLength);
|
||||
// Check the data read was correct.
|
||||
for (int i = 0; i < bytesRead; i++) {
|
||||
assertEquals(testData[dataOffset + accumulatedBytesRead + i],
|
||||
outputBuffer[writeOffset + i]);
|
||||
}
|
||||
// Check that we haven't read more data than we were expecting.
|
||||
accumulatedBytesRead += bytesRead;
|
||||
assertTrue(accumulatedBytesRead <= expectedFinalBytesRead);
|
||||
// If we haven't read all of the bytes the request should have been satisfied in full.
|
||||
assertTrue(accumulatedBytesRead == expectedFinalBytesRead
|
||||
|| bytesRead == requestedReadLength);
|
||||
} else {
|
||||
// We're done. Check we read the expected number of bytes.
|
||||
assertEquals(expectedFinalBytesRead, accumulatedBytesRead);
|
||||
return;
|
||||
}
|
||||
}
|
||||
} catch (IOException e) {
|
||||
if (expectFailOnOpen && !opened) {
|
||||
// Expected.
|
||||
return;
|
||||
}
|
||||
// Unexpected failure.
|
||||
fail();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,60 @@
|
|||
/*
|
||||
* 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.upstream;
|
||||
|
||||
import com.google.android.exoplayer.testutil.Util;
|
||||
|
||||
import junit.framework.TestCase;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.Arrays;
|
||||
|
||||
/**
|
||||
* Unit tests for {@link DataSourceStream}.
|
||||
*/
|
||||
public class DataSourceStreamTest extends TestCase {
|
||||
|
||||
private static final int DATA_LENGTH = 1024;
|
||||
private static final int BUFFER_LENGTH = 128;
|
||||
|
||||
public void testGetLoadedData() throws IOException, InterruptedException {
|
||||
byte[] testData = Util.buildTestData(DATA_LENGTH);
|
||||
DataSource dataSource = new ByteArrayDataSource(testData);
|
||||
DataSpec dataSpec = new DataSpec(null, 0, DATA_LENGTH, null);
|
||||
DataSourceStream dataSourceStream = new DataSourceStream(dataSource, dataSpec,
|
||||
new BufferPool(BUFFER_LENGTH));
|
||||
|
||||
dataSourceStream.load();
|
||||
// Assert that the read and load positions are correct.
|
||||
assertEquals(0, dataSourceStream.getReadPosition());
|
||||
assertEquals(testData.length, dataSourceStream.getLoadPosition());
|
||||
|
||||
int halfTestDataLength = testData.length / 2;
|
||||
byte[] readData = new byte[testData.length];
|
||||
int bytesRead = dataSourceStream.read(readData, 0, halfTestDataLength);
|
||||
// Assert that the read position is updated correctly.
|
||||
assertEquals(halfTestDataLength, bytesRead);
|
||||
assertEquals(halfTestDataLength, dataSourceStream.getReadPosition());
|
||||
|
||||
bytesRead += dataSourceStream.read(readData, bytesRead, testData.length - bytesRead);
|
||||
// Assert that the read position was updated correctly.
|
||||
assertEquals(testData.length, bytesRead);
|
||||
assertEquals(testData.length, dataSourceStream.getReadPosition());
|
||||
// Assert that the data read using the two read calls either side of getLoadedData is correct.
|
||||
assertTrue(Arrays.equals(testData, readData));
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,124 @@
|
|||
/*
|
||||
* 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.util;
|
||||
|
||||
import junit.framework.TestCase;
|
||||
|
||||
import java.util.Arrays;
|
||||
|
||||
/**
|
||||
* Tests for {@link ParsableByteArray}.
|
||||
*/
|
||||
public class ParsableByteArrayTest extends TestCase {
|
||||
|
||||
private static final byte[] ARRAY_ELEMENTS =
|
||||
new byte[] {0x0F, (byte) 0xFF, (byte) 0x42, (byte) 0x0F, 0x00, 0x00, 0x00, 0x00};
|
||||
|
||||
private ParsableByteArray parsableByteArray;
|
||||
|
||||
@Override
|
||||
public void setUp() {
|
||||
parsableByteArray = new ParsableByteArray(ARRAY_ELEMENTS.length);
|
||||
System.arraycopy(ARRAY_ELEMENTS, 0, parsableByteArray.data, 0, ARRAY_ELEMENTS.length);
|
||||
}
|
||||
|
||||
public void testReadInt() {
|
||||
// When reading a signed integer
|
||||
int value = parsableByteArray.readInt();
|
||||
|
||||
// Then the read value is equal to the array elements interpreted as an int.
|
||||
assertEquals((0xFF & ARRAY_ELEMENTS[0]) << 24 | (0xFF & ARRAY_ELEMENTS[1]) << 16
|
||||
| (0xFF & ARRAY_ELEMENTS[2]) << 8 | (0xFF & ARRAY_ELEMENTS[3]), value);
|
||||
}
|
||||
|
||||
public void testSkipBack() {
|
||||
// When reading an unsigned integer
|
||||
long value = parsableByteArray.readUnsignedInt();
|
||||
|
||||
// Then skipping back and reading gives the same value.
|
||||
parsableByteArray.skip(-4);
|
||||
assertEquals(value, parsableByteArray.readUnsignedInt());
|
||||
}
|
||||
|
||||
public void testReadingMovesPosition() {
|
||||
// Given an array at the start
|
||||
assertEquals(0, parsableByteArray.getPosition());
|
||||
|
||||
// When reading an integer, the position advances
|
||||
parsableByteArray.readUnsignedInt();
|
||||
assertEquals(4, parsableByteArray.getPosition());
|
||||
}
|
||||
|
||||
public void testOutOfBoundsThrows() {
|
||||
// Given an array at the end
|
||||
parsableByteArray.readUnsignedLongToLong();
|
||||
assertEquals(ARRAY_ELEMENTS.length, parsableByteArray.getPosition());
|
||||
|
||||
// Then reading more data throws.
|
||||
try {
|
||||
parsableByteArray.readUnsignedInt();
|
||||
fail();
|
||||
} catch (Exception e) {
|
||||
// Expected.
|
||||
}
|
||||
}
|
||||
|
||||
public void testModificationsAffectParsableArray() {
|
||||
// When modifying the wrapped byte array
|
||||
byte[] data = parsableByteArray.data;
|
||||
long readValue = parsableByteArray.readUnsignedInt();
|
||||
data[0] = (byte) (ARRAY_ELEMENTS[0] + 1);
|
||||
parsableByteArray.setPosition(0);
|
||||
|
||||
// Then the parsed value changes.
|
||||
assertFalse(parsableByteArray.readUnsignedInt() == readValue);
|
||||
}
|
||||
|
||||
public void testReadingUnsignedLongWithMsbSetThrows() {
|
||||
// Given an array with the most-significant bit set on the top byte
|
||||
byte[] data = parsableByteArray.data;
|
||||
data[0] = (byte) 0x80;
|
||||
|
||||
// Then reading an unsigned long throws.
|
||||
try {
|
||||
parsableByteArray.readUnsignedLongToLong();
|
||||
fail();
|
||||
} catch (Exception e) {
|
||||
// Expected.
|
||||
}
|
||||
}
|
||||
|
||||
public void testReadUnsignedFixedPoint1616() {
|
||||
// When reading the integer part of a 16.16 fixed point value
|
||||
int value = parsableByteArray.readUnsignedFixedPoint1616();
|
||||
|
||||
// Then the read value is equal to the array elements interpreted as a short.
|
||||
assertEquals((0xFF & ARRAY_ELEMENTS[0]) << 8 | (ARRAY_ELEMENTS[1] & 0xFF), value);
|
||||
assertEquals(4, parsableByteArray.getPosition());
|
||||
}
|
||||
|
||||
public void testReadingBytesReturnsCopy() {
|
||||
// When reading all the bytes back
|
||||
int length = parsableByteArray.limit();
|
||||
assertEquals(ARRAY_ELEMENTS.length, length);
|
||||
byte[] copy = new byte[length];
|
||||
parsableByteArray.readBytes(copy, 0, length);
|
||||
|
||||
// Then the array elements are the same.
|
||||
assertTrue(Arrays.equals(parsableByteArray.data, copy));
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,98 @@
|
|||
/*
|
||||
* 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.util;
|
||||
|
||||
import junit.framework.TestCase;
|
||||
|
||||
/**
|
||||
* Unit tests for {@link UriUtil}.
|
||||
*/
|
||||
public class UriUtilTest extends TestCase {
|
||||
|
||||
/**
|
||||
* Tests normal usage of {@link UriUtil#resolve(String, String)}.
|
||||
* <p>
|
||||
* The test cases are taken from RFC-3986 5.4.1.
|
||||
*/
|
||||
public void testResolveNormal() {
|
||||
String base = "http://a/b/c/d;p?q";
|
||||
|
||||
assertEquals("g:h", UriUtil.resolve(base, "g:h"));
|
||||
assertEquals("http://a/b/c/g", UriUtil.resolve(base, "g"));
|
||||
assertEquals("http://a/b/c/g/", UriUtil.resolve(base, "g/"));
|
||||
assertEquals("http://a/g", UriUtil.resolve(base, "/g"));
|
||||
assertEquals("http://g", UriUtil.resolve(base, "//g"));
|
||||
assertEquals("http://a/b/c/d;p?y", UriUtil.resolve(base, "?y"));
|
||||
assertEquals("http://a/b/c/g?y", UriUtil.resolve(base, "g?y"));
|
||||
assertEquals("http://a/b/c/d;p?q#s", UriUtil.resolve(base, "#s"));
|
||||
assertEquals("http://a/b/c/g#s", UriUtil.resolve(base, "g#s"));
|
||||
assertEquals("http://a/b/c/g?y#s", UriUtil.resolve(base, "g?y#s"));
|
||||
assertEquals("http://a/b/c/;x", UriUtil.resolve(base, ";x"));
|
||||
assertEquals("http://a/b/c/g;x", UriUtil.resolve(base, "g;x"));
|
||||
assertEquals("http://a/b/c/g;x?y#s", UriUtil.resolve(base, "g;x?y#s"));
|
||||
assertEquals("http://a/b/c/d;p?q", UriUtil.resolve(base, ""));
|
||||
assertEquals("http://a/b/c/", UriUtil.resolve(base, "."));
|
||||
assertEquals("http://a/b/c/", UriUtil.resolve(base, "./"));
|
||||
assertEquals("http://a/b/", UriUtil.resolve(base, ".."));
|
||||
assertEquals("http://a/b/", UriUtil.resolve(base, "../"));
|
||||
assertEquals("http://a/b/g", UriUtil.resolve(base, "../g"));
|
||||
assertEquals("http://a/", UriUtil.resolve(base, "../.."));
|
||||
assertEquals("http://a/", UriUtil.resolve(base, "../../"));
|
||||
assertEquals("http://a/g", UriUtil.resolve(base, "../../g"));
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests abnormal usage of {@link UriUtil#resolve(String, String)}.
|
||||
* <p>
|
||||
* The test cases are taken from RFC-3986 5.4.2.
|
||||
*/
|
||||
public void testResolveAbnormal() {
|
||||
String base = "http://a/b/c/d;p?q";
|
||||
|
||||
assertEquals("http://a/g", UriUtil.resolve(base, "../../../g"));
|
||||
assertEquals("http://a/g", UriUtil.resolve(base, "../../../../g"));
|
||||
|
||||
assertEquals("http://a/g", UriUtil.resolve(base, "/./g"));
|
||||
assertEquals("http://a/g", UriUtil.resolve(base, "/../g"));
|
||||
assertEquals("http://a/b/c/g.", UriUtil.resolve(base, "g."));
|
||||
assertEquals("http://a/b/c/.g", UriUtil.resolve(base, ".g"));
|
||||
assertEquals("http://a/b/c/g..", UriUtil.resolve(base, "g.."));
|
||||
assertEquals("http://a/b/c/..g", UriUtil.resolve(base, "..g"));
|
||||
|
||||
assertEquals("http://a/b/g", UriUtil.resolve(base, "./../g"));
|
||||
assertEquals("http://a/b/c/g/", UriUtil.resolve(base, "./g/."));
|
||||
assertEquals("http://a/b/c/g/h", UriUtil.resolve(base, "g/./h"));
|
||||
assertEquals("http://a/b/c/h", UriUtil.resolve(base, "g/../h"));
|
||||
assertEquals("http://a/b/c/g;x=1/y", UriUtil.resolve(base, "g;x=1/./y"));
|
||||
assertEquals("http://a/b/c/y", UriUtil.resolve(base, "g;x=1/../y"));
|
||||
|
||||
assertEquals("http://a/b/c/g?y/./x", UriUtil.resolve(base, "g?y/./x"));
|
||||
assertEquals("http://a/b/c/g?y/../x", UriUtil.resolve(base, "g?y/../x"));
|
||||
assertEquals("http://a/b/c/g#s/./x", UriUtil.resolve(base, "g#s/./x"));
|
||||
assertEquals("http://a/b/c/g#s/../x", UriUtil.resolve(base, "g#s/../x"));
|
||||
|
||||
assertEquals("http:g", UriUtil.resolve(base, "http:g"));
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests additional abnormal usage of {@link UriUtil#resolve(String, String)}.
|
||||
*/
|
||||
public void testResolveAbnormalAdditional() {
|
||||
assertEquals("c:e", UriUtil.resolve("http://a/b", "c:d/../e"));
|
||||
assertEquals("a:c", UriUtil.resolve("a:b", "../c"));
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,147 @@
|
|||
/*
|
||||
* 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.util;
|
||||
|
||||
import junit.framework.TestCase;
|
||||
|
||||
import java.text.ParseException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* Unit tests for {@link Util}.
|
||||
*/
|
||||
public class UtilTest extends TestCase {
|
||||
|
||||
public void testArrayBinarySearchFloor() {
|
||||
long[] values = new long[0];
|
||||
assertEquals(-1, Util.binarySearchFloor(values, 0, false, false));
|
||||
assertEquals(0, Util.binarySearchFloor(values, 0, false, true));
|
||||
|
||||
values = new long[] {1, 3, 5};
|
||||
assertEquals(-1, Util.binarySearchFloor(values, 0, false, false));
|
||||
assertEquals(-1, Util.binarySearchFloor(values, 0, true, false));
|
||||
assertEquals(0, Util.binarySearchFloor(values, 0, false, true));
|
||||
assertEquals(0, Util.binarySearchFloor(values, 0, true, true));
|
||||
|
||||
assertEquals(-1, Util.binarySearchFloor(values, 1, false, false));
|
||||
assertEquals(0, Util.binarySearchFloor(values, 1, true, false));
|
||||
assertEquals(0, Util.binarySearchFloor(values, 1, false, true));
|
||||
assertEquals(0, Util.binarySearchFloor(values, 1, true, true));
|
||||
|
||||
assertEquals(1, Util.binarySearchFloor(values, 4, false, false));
|
||||
assertEquals(1, Util.binarySearchFloor(values, 4, true, false));
|
||||
|
||||
assertEquals(1, Util.binarySearchFloor(values, 5, false, false));
|
||||
assertEquals(2, Util.binarySearchFloor(values, 5, true, false));
|
||||
|
||||
assertEquals(2, Util.binarySearchFloor(values, 6, false, false));
|
||||
assertEquals(2, Util.binarySearchFloor(values, 6, true, false));
|
||||
}
|
||||
|
||||
public void testListBinarySearchFloor() {
|
||||
List<Integer> values = new ArrayList<Integer>();
|
||||
assertEquals(-1, Util.binarySearchFloor(values, 0, false, false));
|
||||
assertEquals(0, Util.binarySearchFloor(values, 0, false, true));
|
||||
|
||||
values.add(1);
|
||||
values.add(3);
|
||||
values.add(5);
|
||||
assertEquals(-1, Util.binarySearchFloor(values, 0, false, false));
|
||||
assertEquals(-1, Util.binarySearchFloor(values, 0, true, false));
|
||||
assertEquals(0, Util.binarySearchFloor(values, 0, false, true));
|
||||
assertEquals(0, Util.binarySearchFloor(values, 0, true, true));
|
||||
|
||||
assertEquals(-1, Util.binarySearchFloor(values, 1, false, false));
|
||||
assertEquals(0, Util.binarySearchFloor(values, 1, true, false));
|
||||
assertEquals(0, Util.binarySearchFloor(values, 1, false, true));
|
||||
assertEquals(0, Util.binarySearchFloor(values, 1, true, true));
|
||||
|
||||
assertEquals(1, Util.binarySearchFloor(values, 4, false, false));
|
||||
assertEquals(1, Util.binarySearchFloor(values, 4, true, false));
|
||||
|
||||
assertEquals(1, Util.binarySearchFloor(values, 5, false, false));
|
||||
assertEquals(2, Util.binarySearchFloor(values, 5, true, false));
|
||||
|
||||
assertEquals(2, Util.binarySearchFloor(values, 6, false, false));
|
||||
assertEquals(2, Util.binarySearchFloor(values, 6, true, false));
|
||||
}
|
||||
|
||||
public void testArrayBinarySearchCeil() {
|
||||
long[] values = new long[0];
|
||||
assertEquals(0, Util.binarySearchCeil(values, 0, false, false));
|
||||
assertEquals(-1, Util.binarySearchCeil(values, 0, false, true));
|
||||
|
||||
values = new long[] {1, 3, 5};
|
||||
assertEquals(0, Util.binarySearchCeil(values, 0, false, false));
|
||||
assertEquals(0, Util.binarySearchCeil(values, 0, true, false));
|
||||
|
||||
assertEquals(1, Util.binarySearchCeil(values, 1, false, false));
|
||||
assertEquals(0, Util.binarySearchCeil(values, 1, true, false));
|
||||
|
||||
assertEquals(1, Util.binarySearchCeil(values, 2, false, false));
|
||||
assertEquals(1, Util.binarySearchCeil(values, 2, true, false));
|
||||
|
||||
assertEquals(3, Util.binarySearchCeil(values, 5, false, false));
|
||||
assertEquals(2, Util.binarySearchCeil(values, 5, true, false));
|
||||
assertEquals(2, Util.binarySearchCeil(values, 5, false, true));
|
||||
assertEquals(2, Util.binarySearchCeil(values, 5, true, true));
|
||||
|
||||
assertEquals(3, Util.binarySearchCeil(values, 6, false, false));
|
||||
assertEquals(3, Util.binarySearchCeil(values, 6, true, false));
|
||||
assertEquals(2, Util.binarySearchCeil(values, 6, false, true));
|
||||
assertEquals(2, Util.binarySearchCeil(values, 6, true, true));
|
||||
}
|
||||
|
||||
public void testListBinarySearchCeil() {
|
||||
List<Integer> values = new ArrayList<Integer>();
|
||||
assertEquals(0, Util.binarySearchCeil(values, 0, false, false));
|
||||
assertEquals(-1, Util.binarySearchCeil(values, 0, false, true));
|
||||
|
||||
values.add(1);
|
||||
values.add(3);
|
||||
values.add(5);
|
||||
assertEquals(0, Util.binarySearchCeil(values, 0, false, false));
|
||||
assertEquals(0, Util.binarySearchCeil(values, 0, true, false));
|
||||
|
||||
assertEquals(1, Util.binarySearchCeil(values, 1, false, false));
|
||||
assertEquals(0, Util.binarySearchCeil(values, 1, true, false));
|
||||
|
||||
assertEquals(1, Util.binarySearchCeil(values, 2, false, false));
|
||||
assertEquals(1, Util.binarySearchCeil(values, 2, true, false));
|
||||
|
||||
assertEquals(3, Util.binarySearchCeil(values, 5, false, false));
|
||||
assertEquals(2, Util.binarySearchCeil(values, 5, true, false));
|
||||
assertEquals(2, Util.binarySearchCeil(values, 5, false, true));
|
||||
assertEquals(2, Util.binarySearchCeil(values, 5, true, true));
|
||||
|
||||
assertEquals(3, Util.binarySearchCeil(values, 6, false, false));
|
||||
assertEquals(3, Util.binarySearchCeil(values, 6, true, false));
|
||||
assertEquals(2, Util.binarySearchCeil(values, 6, false, true));
|
||||
assertEquals(2, Util.binarySearchCeil(values, 6, true, true));
|
||||
}
|
||||
|
||||
public void testParseXsDuration() {
|
||||
assertEquals(150279L, Util.parseXsDuration("PT150.279S"));
|
||||
assertEquals(1500L, Util.parseXsDuration("PT1.500S"));
|
||||
}
|
||||
|
||||
public void testParseXsDateTime() throws ParseException {
|
||||
assertEquals(1403219262000L, Util.parseXsDateTime("2014-06-19T23:07:42"));
|
||||
assertEquals(1407322800000L, Util.parseXsDateTime("2014-08-06T11:00:00Z"));
|
||||
}
|
||||
|
||||
}
|
||||
1
library/src/test/libs/.README.txt
Normal file
1
library/src/test/libs/.README.txt
Normal file
|
|
@ -0,0 +1 @@
|
|||
This file is needed to make sure the libs directory is present.
|
||||
14
library/src/test/project.properties
Normal file
14
library/src/test/project.properties
Normal file
|
|
@ -0,0 +1,14 @@
|
|||
# This file is automatically generated by Android Tools.
|
||||
# Do not modify this file -- YOUR CHANGES WILL BE ERASED!
|
||||
#
|
||||
# This file must be checked in Version Control Systems.
|
||||
#
|
||||
# To customize properties used by the Ant build system edit
|
||||
# "ant.properties", and override values to adapt the script to your
|
||||
# project structure.
|
||||
#
|
||||
# To enable ProGuard to shrink and obfuscate your code, uncomment this (available properties: sdk.dir, user.home):
|
||||
#proguard.config=${sdk.dir}/tools/proguard/proguard-android.txt:proguard-project.txt
|
||||
|
||||
# Project target.
|
||||
target=android-21
|
||||
2
library/src/test/res/.README.txt
Normal file
2
library/src/test/res/.README.txt
Normal file
|
|
@ -0,0 +1,2 @@
|
|||
This file is needed to make sure the res directory is present.
|
||||
The file is ignored by the Android toolchain because its name starts with a dot.
|
||||
201
third_party/dexmaker/LICENSE
vendored
Normal file
201
third_party/dexmaker/LICENSE
vendored
Normal file
|
|
@ -0,0 +1,201 @@
|
|||
Apache License
|
||||
Version 2.0, January 2004
|
||||
http://www.apache.org/licenses/
|
||||
|
||||
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
|
||||
|
||||
1. Definitions.
|
||||
|
||||
"License" shall mean the terms and conditions for use, reproduction,
|
||||
and distribution as defined by Sections 1 through 9 of this document.
|
||||
|
||||
"Licensor" shall mean the copyright owner or entity authorized by
|
||||
the copyright owner that is granting the License.
|
||||
|
||||
"Legal Entity" shall mean the union of the acting entity and all
|
||||
other entities that control, are controlled by, or are under common
|
||||
control with that entity. For the purposes of this definition,
|
||||
"control" means (i) the power, direct or indirect, to cause the
|
||||
direction or management of such entity, whether by contract or
|
||||
otherwise, or (ii) ownership of fifty percent (50%) or more of the
|
||||
outstanding shares, or (iii) beneficial ownership of such entity.
|
||||
|
||||
"You" (or "Your") shall mean an individual or Legal Entity
|
||||
exercising permissions granted by this License.
|
||||
|
||||
"Source" form shall mean the preferred form for making modifications,
|
||||
including but not limited to software source code, documentation
|
||||
source, and configuration files.
|
||||
|
||||
"Object" form shall mean any form resulting from mechanical
|
||||
transformation or translation of a Source form, including but
|
||||
not limited to compiled object code, generated documentation,
|
||||
and conversions to other media types.
|
||||
|
||||
"Work" shall mean the work of authorship, whether in Source or
|
||||
Object form, made available under the License, as indicated by a
|
||||
copyright notice that is included in or attached to the work
|
||||
(an example is provided in the Appendix below).
|
||||
|
||||
"Derivative Works" shall mean any work, whether in Source or Object
|
||||
form, that is based on (or derived from) the Work and for which the
|
||||
editorial revisions, annotations, elaborations, or other modifications
|
||||
represent, as a whole, an original work of authorship. For the purposes
|
||||
of this License, Derivative Works shall not include works that remain
|
||||
separable from, or merely link (or bind by name) to the interfaces of,
|
||||
the Work and Derivative Works thereof.
|
||||
|
||||
"Contribution" shall mean any work of authorship, including
|
||||
the original version of the Work and any modifications or additions
|
||||
to that Work or Derivative Works thereof, that is intentionally
|
||||
submitted to Licensor for inclusion in the Work by the copyright owner
|
||||
or by an individual or Legal Entity authorized to submit on behalf of
|
||||
the copyright owner. For the purposes of this definition, "submitted"
|
||||
means any form of electronic, verbal, or written communication sent
|
||||
to the Licensor or its representatives, including but not limited to
|
||||
communication on electronic mailing lists, source code control systems,
|
||||
and issue tracking systems that are managed by, or on behalf of, the
|
||||
Licensor for the purpose of discussing and improving the Work, but
|
||||
excluding communication that is conspicuously marked or otherwise
|
||||
designated in writing by the copyright owner as "Not a Contribution."
|
||||
|
||||
"Contributor" shall mean Licensor and any individual or Legal Entity
|
||||
on behalf of whom a Contribution has been received by Licensor and
|
||||
subsequently incorporated within the Work.
|
||||
|
||||
2. Grant of Copyright License. Subject to the terms and conditions of
|
||||
this License, each Contributor hereby grants to You a perpetual,
|
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||
copyright license to reproduce, prepare Derivative Works of,
|
||||
publicly display, publicly perform, sublicense, and distribute the
|
||||
Work and such Derivative Works in Source or Object form.
|
||||
|
||||
3. Grant of Patent License. Subject to the terms and conditions of
|
||||
this License, each Contributor hereby grants to You a perpetual,
|
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||
(except as stated in this section) patent license to make, have made,
|
||||
use, offer to sell, sell, import, and otherwise transfer the Work,
|
||||
where such license applies only to those patent claims licensable
|
||||
by such Contributor that are necessarily infringed by their
|
||||
Contribution(s) alone or by combination of their Contribution(s)
|
||||
with the Work to which such Contribution(s) was submitted. If You
|
||||
institute patent litigation against any entity (including a
|
||||
cross-claim or counterclaim in a lawsuit) alleging that the Work
|
||||
or a Contribution incorporated within the Work constitutes direct
|
||||
or contributory patent infringement, then any patent licenses
|
||||
granted to You under this License for that Work shall terminate
|
||||
as of the date such litigation is filed.
|
||||
|
||||
4. Redistribution. You may reproduce and distribute copies of the
|
||||
Work or Derivative Works thereof in any medium, with or without
|
||||
modifications, and in Source or Object form, provided that You
|
||||
meet the following conditions:
|
||||
|
||||
(a) You must give any other recipients of the Work or
|
||||
Derivative Works a copy of this License; and
|
||||
|
||||
(b) You must cause any modified files to carry prominent notices
|
||||
stating that You changed the files; and
|
||||
|
||||
(c) You must retain, in the Source form of any Derivative Works
|
||||
that You distribute, all copyright, patent, trademark, and
|
||||
attribution notices from the Source form of the Work,
|
||||
excluding those notices that do not pertain to any part of
|
||||
the Derivative Works; and
|
||||
|
||||
(d) If the Work includes a "NOTICE" text file as part of its
|
||||
distribution, then any Derivative Works that You distribute must
|
||||
include a readable copy of the attribution notices contained
|
||||
within such NOTICE file, excluding those notices that do not
|
||||
pertain to any part of the Derivative Works, in at least one
|
||||
of the following places: within a NOTICE text file distributed
|
||||
as part of the Derivative Works; within the Source form or
|
||||
documentation, if provided along with the Derivative Works; or,
|
||||
within a display generated by the Derivative Works, if and
|
||||
wherever such third-party notices normally appear. The contents
|
||||
of the NOTICE file are for informational purposes only and
|
||||
do not modify the License. You may add Your own attribution
|
||||
notices within Derivative Works that You distribute, alongside
|
||||
or as an addendum to the NOTICE text from the Work, provided
|
||||
that such additional attribution notices cannot be construed
|
||||
as modifying the License.
|
||||
|
||||
You may add Your own copyright statement to Your modifications and
|
||||
may provide additional or different license terms and conditions
|
||||
for use, reproduction, or distribution of Your modifications, or
|
||||
for any such Derivative Works as a whole, provided Your use,
|
||||
reproduction, and distribution of the Work otherwise complies with
|
||||
the conditions stated in this License.
|
||||
|
||||
5. Submission of Contributions. Unless You explicitly state otherwise,
|
||||
any Contribution intentionally submitted for inclusion in the Work
|
||||
by You to the Licensor shall be under the terms and conditions of
|
||||
this License, without any additional terms or conditions.
|
||||
Notwithstanding the above, nothing herein shall supersede or modify
|
||||
the terms of any separate license agreement you may have executed
|
||||
with Licensor regarding such Contributions.
|
||||
|
||||
6. Trademarks. This License does not grant permission to use the trade
|
||||
names, trademarks, service marks, or product names of the Licensor,
|
||||
except as required for reasonable and customary use in describing the
|
||||
origin of the Work and reproducing the content of the NOTICE file.
|
||||
|
||||
7. Disclaimer of Warranty. Unless required by applicable law or
|
||||
agreed to in writing, Licensor provides the Work (and each
|
||||
Contributor provides its Contributions) on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
|
||||
implied, including, without limitation, any warranties or conditions
|
||||
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
|
||||
PARTICULAR PURPOSE. You are solely responsible for determining the
|
||||
appropriateness of using or redistributing the Work and assume any
|
||||
risks associated with Your exercise of permissions under this License.
|
||||
|
||||
8. Limitation of Liability. In no event and under no legal theory,
|
||||
whether in tort (including negligence), contract, or otherwise,
|
||||
unless required by applicable law (such as deliberate and grossly
|
||||
negligent acts) or agreed to in writing, shall any Contributor be
|
||||
liable to You for damages, including any direct, indirect, special,
|
||||
incidental, or consequential damages of any character arising as a
|
||||
result of this License or out of the use or inability to use the
|
||||
Work (including but not limited to damages for loss of goodwill,
|
||||
work stoppage, computer failure or malfunction, or any and all
|
||||
other commercial damages or losses), even if such Contributor
|
||||
has been advised of the possibility of such damages.
|
||||
|
||||
9. Accepting Warranty or Additional Liability. While redistributing
|
||||
the Work or Derivative Works thereof, You may choose to offer,
|
||||
and charge a fee for, acceptance of support, warranty, indemnity,
|
||||
or other liability obligations and/or rights consistent with this
|
||||
License. However, in accepting such obligations, You may act only
|
||||
on Your own behalf and on Your sole responsibility, not on behalf
|
||||
of any other Contributor, and only if You agree to indemnify,
|
||||
defend, and hold each Contributor harmless for any liability
|
||||
incurred by, or claims asserted against, such Contributor by reason
|
||||
of your accepting any such warranty or additional liability.
|
||||
|
||||
END OF TERMS AND CONDITIONS
|
||||
|
||||
APPENDIX: How to apply the Apache License to your work.
|
||||
|
||||
To apply the Apache License to your work, attach the following
|
||||
boilerplate notice, with the fields enclosed by brackets "[]"
|
||||
replaced with your own identifying information. (Don't include
|
||||
the brackets!) The text should be enclosed in the appropriate
|
||||
comment syntax for the file format. We also recommend that a
|
||||
file or class name and description of purpose be included on the
|
||||
same "printed page" as the copyright notice for easier
|
||||
identification within third-party archives.
|
||||
|
||||
Copyright [yyyy] [name of copyright owner]
|
||||
|
||||
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.
|
||||
BIN
third_party/dexmaker/dexmaker-1.2.jar
vendored
Normal file
BIN
third_party/dexmaker/dexmaker-1.2.jar
vendored
Normal file
Binary file not shown.
BIN
third_party/dexmaker/dexmaker-mockito-1.2.jar
vendored
Normal file
BIN
third_party/dexmaker/dexmaker-mockito-1.2.jar
vendored
Normal file
Binary file not shown.
21
third_party/mockito/LICENSE
vendored
Normal file
21
third_party/mockito/LICENSE
vendored
Normal file
|
|
@ -0,0 +1,21 @@
|
|||
The MIT License
|
||||
|
||||
Copyright (c) 2008-2010 Mockito contributors
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in
|
||||
all copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
THE SOFTWARE.
|
||||
BIN
third_party/mockito/mockito-all-1.9.5.jar
vendored
Normal file
BIN
third_party/mockito/mockito-all-1.9.5.jar
vendored
Normal file
Binary file not shown.
Loading…
Reference in a new issue