Commit 5dedf5d9 by ojw28

Merge pull request #340 from google/dev

dev -> dev-webm-vp9-opus
parents 07855aad fbd0a57e
Showing with 2336 additions and 230 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;
......
/*
* 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";
......
/*
* 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);
}
......
......@@ -26,21 +26,31 @@ import android.net.Uri;
public final class DataSpec {
/**
* Identifies the source from which data should be read.
* 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 final Uri uri;
public static final int FLAG_ALLOW_GZIP = 1;
/**
* 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.
* Identifies the source from which data should be read.
*/
public final boolean uriIsFullStream;
public final Uri uri;
/**
* 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;
}
......
......@@ -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;
......@@ -135,54 +134,6 @@ public final class Util {
}
/**
* 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.
* <p>
......
<?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>
<?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>
<?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>
<?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>
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.
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.
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&nbsp;the &lt;fourth&gt; &amp;subtitle.
/*
* 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);
}
}
/*
* 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());
}
}
/*
* 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);
}
}
/*
* 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);
}
}
/*
* 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());
}
}
/*
* 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.
}
}
}
/*
* 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());
}
}
}
/*
* 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());
}
}
}
/*
* 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());
}
}
}
/*
* 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]);
}
}
/*
* 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;
}
}
/*
* 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));
}
}
/*
* 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));
}
}
/*
* 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();
}
}
}
/*
* 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));
}
}
/*
* 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));
}
}
/*
* 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"));
}
}
/*
* 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"));
}
}
This file is needed to make sure the libs directory is present.
# 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
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.
No preview for this file type
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.
No preview for this file type
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or sign in to comment