();
+ 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);
+ }
+
+}
diff --git a/library/src/test/java/com/google/android/exoplayer/chunk/parser/webm/DefaultEbmlReaderTest.java b/library/src/test/java/com/google/android/exoplayer/chunk/parser/webm/DefaultEbmlReaderTest.java
new file mode 100644
index 0000000000..5d4a30cab9
--- /dev/null
+++ b/library/src/test/java/com/google/android/exoplayer/chunk/parser/webm/DefaultEbmlReaderTest.java
@@ -0,0 +1,350 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.google.android.exoplayer.chunk.parser.webm;
+
+import com.google.android.exoplayer.ParserException;
+import com.google.android.exoplayer.upstream.ByteArrayNonBlockingInputStream;
+import com.google.android.exoplayer.upstream.NonBlockingInputStream;
+
+import junit.framework.TestCase;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.List;
+
+/**
+ * Tests {@link DefaultEbmlReader}.
+ */
+public class DefaultEbmlReaderTest extends TestCase {
+
+ private final EventCapturingEbmlEventHandler eventHandler =
+ new EventCapturingEbmlEventHandler();
+
+ public void testNothing() {
+ NonBlockingInputStream input = createTestInputStream();
+ assertNoEvents(input, EbmlReader.READ_RESULT_END_OF_STREAM);
+ }
+
+ public void testMasterElement() {
+ NonBlockingInputStream input =
+ createTestInputStream(0x1A, 0x45, 0xDF, 0xA3, 0x84, 0x42, 0x85, 0x81, 0x01);
+ EventCapturingEbmlEventHandler expected = new EventCapturingEbmlEventHandler();
+ expected.onMasterElementStart(EventCapturingEbmlEventHandler.ID_EBML, 0, 5, 4);
+ expected.onIntegerElement(EventCapturingEbmlEventHandler.ID_DOC_TYPE_READ_VERSION, 1);
+ expected.onMasterElementEnd(EventCapturingEbmlEventHandler.ID_EBML);
+ assertEvents(input, EbmlReader.READ_RESULT_END_OF_STREAM, expected.events);
+ }
+
+ public void testMasterElementEmpty() {
+ NonBlockingInputStream input = createTestInputStream(0x18, 0x53, 0x80, 0x67, 0x80);
+ EventCapturingEbmlEventHandler expected = new EventCapturingEbmlEventHandler();
+ expected.onMasterElementStart(EventCapturingEbmlEventHandler.ID_SEGMENT, 0, 5, 0);
+ expected.onMasterElementEnd(EventCapturingEbmlEventHandler.ID_SEGMENT);
+ assertEvents(input, EbmlReader.READ_RESULT_END_OF_STREAM, expected.events);
+ }
+
+ public void testUnsignedIntegerElement() {
+ // 0xFE is chosen because for signed integers it should be interpreted as -2
+ NonBlockingInputStream input = createTestInputStream(0x42, 0xF7, 0x81, 0xFE);
+ EventCapturingEbmlEventHandler expected = new EventCapturingEbmlEventHandler();
+ expected.onIntegerElement(EventCapturingEbmlEventHandler.ID_EBML_READ_VERSION, 254);
+ assertEvents(input, EbmlReader.READ_RESULT_END_OF_STREAM, expected.events);
+ }
+
+ public void testUnsignedIntegerElementLarge() {
+ NonBlockingInputStream input =
+ createTestInputStream(0x42, 0xF7, 0x88, 0x7F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF);
+ EventCapturingEbmlEventHandler expected = new EventCapturingEbmlEventHandler();
+ expected.onIntegerElement(EventCapturingEbmlEventHandler.ID_EBML_READ_VERSION, Long.MAX_VALUE);
+ assertEvents(input, EbmlReader.READ_RESULT_END_OF_STREAM, expected.events);
+ }
+
+ public void testUnsignedIntegerElementTooLargeBecomesNegative() {
+ NonBlockingInputStream input =
+ createTestInputStream(0x42, 0xF7, 0x88, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF);
+ EventCapturingEbmlEventHandler expected = new EventCapturingEbmlEventHandler();
+ expected.onIntegerElement(EventCapturingEbmlEventHandler.ID_EBML_READ_VERSION, -1);
+ assertEvents(input, EbmlReader.READ_RESULT_END_OF_STREAM, expected.events);
+ }
+
+ public void testStringElement() {
+ NonBlockingInputStream input =
+ createTestInputStream(0x42, 0x82, 0x86, 0x41, 0x62, 0x63, 0x31, 0x32, 0x33);
+ EventCapturingEbmlEventHandler expected = new EventCapturingEbmlEventHandler();
+ expected.onStringElement(EventCapturingEbmlEventHandler.ID_DOC_TYPE, "Abc123");
+ assertEvents(input, EbmlReader.READ_RESULT_END_OF_STREAM, expected.events);
+ }
+
+ public void testStringElementEmpty() {
+ NonBlockingInputStream input = createTestInputStream(0x42, 0x82, 0x80);
+ EventCapturingEbmlEventHandler expected = new EventCapturingEbmlEventHandler();
+ expected.onStringElement(EventCapturingEbmlEventHandler.ID_DOC_TYPE, "");
+ assertEvents(input, EbmlReader.READ_RESULT_END_OF_STREAM, expected.events);
+ }
+
+ public void testFloatElementThreeBytes() {
+ try {
+ eventHandler.read(createTestInputStream(0x44, 0x89, 0x83, 0x3F, 0x80, 0x00));
+ fail();
+ } catch (IllegalStateException exception) {
+ // Expected
+ }
+ assertNoEvents();
+ }
+
+ public void testFloatElementFourBytes() {
+ NonBlockingInputStream input =
+ createTestInputStream(0x44, 0x89, 0x84, 0x3F, 0x80, 0x00, 0x00);
+ EventCapturingEbmlEventHandler expected = new EventCapturingEbmlEventHandler();
+ expected.onFloatElement(EventCapturingEbmlEventHandler.ID_DURATION, 1.0);
+ assertEvents(input, EbmlReader.READ_RESULT_END_OF_STREAM, expected.events);
+ }
+
+ public void testFloatElementEightBytes() {
+ NonBlockingInputStream input =
+ createTestInputStream(0x44, 0x89, 0x88, 0xC0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00);
+
+ EventCapturingEbmlEventHandler expected = new EventCapturingEbmlEventHandler();
+ expected.onFloatElement(EventCapturingEbmlEventHandler.ID_DURATION, -2.0);
+ assertEvents(input, EbmlReader.READ_RESULT_END_OF_STREAM, expected.events);
+ }
+
+ public void testBinaryElementReadBytes() {
+ eventHandler.binaryElementHandler = EventCapturingEbmlEventHandler.HANDLER_READ_BYTES;
+ NonBlockingInputStream input =
+ createTestInputStream(0xA3, 0x88, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08);
+
+ EventCapturingEbmlEventHandler expected = new EventCapturingEbmlEventHandler();
+ expected.binaryElementHandler = EventCapturingEbmlEventHandler.HANDLER_READ_BYTES;
+ expected.onBinaryElement(
+ EventCapturingEbmlEventHandler.ID_SIMPLE_BLOCK, 0, 0, 8,
+ createTestInputStream(0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08));
+ assertEvents(input, EbmlReader.READ_RESULT_END_OF_STREAM, expected.events);
+ }
+
+ public void testBinaryElementReadVarint() {
+ eventHandler.binaryElementHandler = EventCapturingEbmlEventHandler.HANDLER_READ_VARINT;
+ NonBlockingInputStream input = createTestInputStream(0xA3, 0x82, 0x40, 0x2A);
+
+ EventCapturingEbmlEventHandler expected = new EventCapturingEbmlEventHandler();
+ expected.binaryElementHandler = EventCapturingEbmlEventHandler.HANDLER_READ_VARINT;
+ expected.onBinaryElement(
+ EventCapturingEbmlEventHandler.ID_SIMPLE_BLOCK, 0, 0, 0,
+ createTestInputStream(0x40, 0x2A));
+ assertEvents(input, EbmlReader.READ_RESULT_END_OF_STREAM, expected.events);
+ }
+
+ public void testBinaryElementSkipBytes() {
+ eventHandler.binaryElementHandler = EventCapturingEbmlEventHandler.HANDLER_SKIP_BYTES;
+ NonBlockingInputStream input =
+ createTestInputStream(0xA3, 0x88, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08);
+
+ EventCapturingEbmlEventHandler expected = new EventCapturingEbmlEventHandler();
+ expected.binaryElementHandler = EventCapturingEbmlEventHandler.HANDLER_SKIP_BYTES;
+ expected.onBinaryElement(
+ EventCapturingEbmlEventHandler.ID_SIMPLE_BLOCK, 0, 0, 8,
+ createTestInputStream(0, 0, 0, 0, 0, 0, 0, 0));
+ assertEvents(input, EbmlReader.READ_RESULT_END_OF_STREAM, expected.events);
+ }
+
+ public void testBinaryElementDoNothing() {
+ eventHandler.binaryElementHandler = EventCapturingEbmlEventHandler.HANDLER_DO_NOTHING;
+ try {
+ eventHandler.read(
+ createTestInputStream(0xA3, 0x88, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08));
+ fail();
+ } catch (IllegalStateException exception) {
+ // Expected
+ }
+ assertNoEvents();
+ }
+
+ public void testBinaryElementNotEnoughBytes() {
+ NonBlockingInputStream input = createTestInputStream(0xA3, 0x88, 0x01, 0x02, 0x03);
+ assertNoEvents(input, EbmlReader.READ_RESULT_NEED_MORE_DATA);
+ }
+
+ public void testUnknownElement() {
+ NonBlockingInputStream input = createTestInputStream(0xEC, 0x81, 0x00);
+ assertNoEvents(input, EbmlReader.READ_RESULT_END_OF_STREAM);
+ }
+
+ /**
+ * Helper to build a {@link ByteArrayNonBlockingInputStream} quickly from zero or more
+ * integer arguments.
+ *
+ * Each argument must be able to cast to a byte value.
+ *
+ * @param data Zero or more integers with values between {@code 0x00} and {@code 0xFF}
+ * @return A {@link ByteArrayNonBlockingInputStream} containing the given byte values
+ */
+ private NonBlockingInputStream createTestInputStream(int... data) {
+ byte[] bytes = new byte[data.length];
+ for (int i = 0; i < data.length; i++) {
+ bytes[i] = (byte) data[i];
+ }
+ return new ByteArrayNonBlockingInputStream(bytes);
+ }
+
+ private void assertReads(NonBlockingInputStream input, int continues, int finalResult) {
+ for (int i = 0; i < continues; i++) {
+ assertEquals(EbmlReader.READ_RESULT_CONTINUE, eventHandler.read(input));
+ }
+ assertEquals(finalResult, eventHandler.read(input));
+ }
+
+ private void assertNoEvents() {
+ assertEvents(Collections.emptyList());
+ }
+
+ private void assertEvents(List events) {
+ assertEquals(events.size(), eventHandler.events.size());
+ for (int i = 0; i < events.size(); i++) {
+ assertEquals(events.get(i), eventHandler.events.get(i));
+ }
+ }
+
+ private void assertNoEvents(NonBlockingInputStream input, int finalResult) {
+ assertReads(input, 0, finalResult);
+ assertNoEvents();
+ }
+
+ private void assertEvents(NonBlockingInputStream input, int finalResult, List events) {
+ assertReads(input, events.size(), finalResult);
+ assertEvents(events);
+ }
+
+ /**
+ * An {@link EbmlEventHandler} which captures all event callbacks made by
+ * {@link DefaultEbmlReader} for testing purposes.
+ */
+ private static final class EventCapturingEbmlEventHandler implements EbmlEventHandler {
+
+ // Element IDs
+ private static final int ID_EBML = 0x1A45DFA3;
+ private static final int ID_EBML_READ_VERSION = 0x42F7;
+ private static final int ID_DOC_TYPE = 0x4282;
+ private static final int ID_DOC_TYPE_READ_VERSION = 0x4285;
+
+ private static final int ID_SEGMENT = 0x18538067;
+ private static final int ID_DURATION = 0x4489;
+ private static final int ID_SIMPLE_BLOCK = 0xA3;
+
+ // Various ways to handle things in onBinaryElement()
+ private static final int HANDLER_DO_NOTHING = 0;
+ private static final int HANDLER_READ_BYTES = 1;
+ private static final int HANDLER_READ_VARINT = 2;
+ private static final int HANDLER_SKIP_BYTES = 3;
+
+ private final EbmlReader reader = new DefaultEbmlReader();
+ private final List events = new ArrayList();
+
+ private int binaryElementHandler;
+
+ private EventCapturingEbmlEventHandler() {
+ reader.setEventHandler(this);
+ }
+
+ private int read(NonBlockingInputStream inputStream) {
+ try {
+ return reader.read(inputStream);
+ } catch (ParserException e) {
+ // should never happen.
+ fail();
+ return -1;
+ }
+ }
+
+ @Override
+ public int getElementType(int id) {
+ switch (id) {
+ case ID_EBML:
+ case ID_SEGMENT:
+ return EbmlReader.TYPE_MASTER;
+ case ID_EBML_READ_VERSION:
+ case ID_DOC_TYPE_READ_VERSION:
+ return EbmlReader.TYPE_UNSIGNED_INT;
+ case ID_DOC_TYPE:
+ return EbmlReader.TYPE_STRING;
+ case ID_SIMPLE_BLOCK:
+ return EbmlReader.TYPE_BINARY;
+ case ID_DURATION:
+ return EbmlReader.TYPE_FLOAT;
+ default:
+ return EbmlReader.TYPE_UNKNOWN;
+ }
+ }
+
+ @Override
+ public void onMasterElementStart(
+ int id, long elementOffset, int headerSize, long contentsSize) {
+ events.add(formatEvent(id, "start elementOffset=" + elementOffset
+ + " headerSize=" + headerSize + " contentsSize=" + contentsSize));
+ }
+
+ @Override
+ public void onMasterElementEnd(int id) {
+ events.add(formatEvent(id, "end"));
+ }
+
+ @Override
+ public void onIntegerElement(int id, long value) {
+ events.add(formatEvent(id, "integer=" + String.valueOf(value)));
+ }
+
+ @Override
+ public void onFloatElement(int id, double value) {
+ events.add(formatEvent(id, "float=" + String.valueOf(value)));
+ }
+
+ @Override
+ public void onStringElement(int id, String value) {
+ events.add(formatEvent(id, "string=" + value));
+ }
+
+ @Override
+ public boolean onBinaryElement(
+ int id, long elementOffset, int headerSize, int contentsSize,
+ NonBlockingInputStream inputStream) {
+ switch (binaryElementHandler) {
+ case HANDLER_READ_BYTES:
+ byte[] bytes = new byte[contentsSize];
+ reader.readBytes(inputStream, bytes, contentsSize);
+ events.add(formatEvent(id, "bytes=" + Arrays.toString(bytes)));
+ break;
+ case HANDLER_READ_VARINT:
+ long value = reader.readVarint(inputStream);
+ events.add(formatEvent(id, "varint=" + String.valueOf(value)));
+ break;
+ case HANDLER_SKIP_BYTES:
+ reader.skipBytes(inputStream, contentsSize);
+ events.add(formatEvent(id, "skipped " + contentsSize + " byte(s)"));
+ break;
+ case HANDLER_DO_NOTHING:
+ default:
+ // pass
+ }
+ return true;
+ }
+
+ private static String formatEvent(int id, String event) {
+ return "[" + Integer.toHexString(id) + "] " + event;
+ }
+
+ }
+
+}
diff --git a/library/src/test/java/com/google/android/exoplayer/chunk/parser/webm/WebmExtractorTest.java b/library/src/test/java/com/google/android/exoplayer/chunk/parser/webm/WebmExtractorTest.java
new file mode 100644
index 0000000000..757db52ae2
--- /dev/null
+++ b/library/src/test/java/com/google/android/exoplayer/chunk/parser/webm/WebmExtractorTest.java
@@ -0,0 +1,523 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.google.android.exoplayer.chunk.parser.webm;
+
+import com.google.android.exoplayer.C;
+import com.google.android.exoplayer.MediaFormat;
+import com.google.android.exoplayer.ParserException;
+import com.google.android.exoplayer.SampleHolder;
+import com.google.android.exoplayer.chunk.parser.SegmentIndex;
+import com.google.android.exoplayer.upstream.ByteArrayNonBlockingInputStream;
+import com.google.android.exoplayer.upstream.NonBlockingInputStream;
+import com.google.android.exoplayer.util.MimeTypes;
+
+import android.test.InstrumentationTestCase;
+
+import java.io.IOException;
+import java.nio.ByteBuffer;
+import java.util.Arrays;
+
+public class WebmExtractorTest extends InstrumentationTestCase {
+
+ private static final int INFO_ELEMENT_BYTE_SIZE = 31;
+ private static final int TRACKS_ELEMENT_BYTE_SIZE = 48;
+ private static final int CUES_ELEMENT_BYTE_SIZE = 12;
+ private static final int CUE_POINT_ELEMENT_BYTE_SIZE = 31;
+
+ private static final int DEFAULT_TIMECODE_SCALE = 1000000;
+
+ private static final long TEST_DURATION_US = 9920000L;
+ private static final int TEST_WIDTH = 1280;
+ private static final int TEST_HEIGHT = 720;
+ private static final int TEST_CHANNEL_COUNT = 1;
+ private static final int TEST_SAMPLE_RATE = 48000;
+ private static final long TEST_CODEC_DELAY = 6500000;
+ private static final long TEST_SEEK_PRE_ROLL = 80000000;
+ private static final int TEST_OPUS_CODEC_PRIVATE_SIZE = 2;
+ private static final String TEST_VORBIS_CODEC_PRIVATE = "webm/vorbis_codec_private";
+ private static final int TEST_VORBIS_INFO_SIZE = 30;
+ private static final int TEST_VORBIS_BOOKS_SIZE = 4140;
+
+ private static final int ID_VP9 = 0;
+ private static final int ID_OPUS = 1;
+ private static final int ID_VORBIS = 2;
+
+ private static final int EXPECTED_INIT_RESULT = WebmExtractor.RESULT_READ_INIT
+ | WebmExtractor.RESULT_READ_INDEX | WebmExtractor.RESULT_END_OF_STREAM;
+ private static final int EXPECTED_INIT_AND_SAMPLE_RESULT = WebmExtractor.RESULT_READ_INIT
+ | WebmExtractor.RESULT_READ_INDEX | WebmExtractor.RESULT_READ_SAMPLE;
+
+ private final WebmExtractor extractor = new WebmExtractor();
+ private final SampleHolder sampleHolder =
+ new SampleHolder(SampleHolder.BUFFER_REPLACEMENT_MODE_DISABLED);
+
+ @Override
+ public void setUp() {
+ sampleHolder.data = ByteBuffer.allocate(1024);
+ }
+
+ public void testPrepare() throws ParserException {
+ NonBlockingInputStream testInputStream = new ByteArrayNonBlockingInputStream(
+ createInitializationSegment(1, 0, true, DEFAULT_TIMECODE_SCALE, ID_VP9));
+ assertEquals(EXPECTED_INIT_RESULT, extractor.read(testInputStream, sampleHolder));
+ assertFormat();
+ assertIndex(new IndexPoint(0, 0, TEST_DURATION_US));
+ }
+
+ public void testPrepareOpus() throws ParserException {
+ NonBlockingInputStream testInputStream = new ByteArrayNonBlockingInputStream(
+ createInitializationSegment(1, 0, true, DEFAULT_TIMECODE_SCALE, ID_OPUS));
+ assertEquals(EXPECTED_INIT_RESULT, extractor.read(testInputStream, sampleHolder));
+ assertAudioFormat(ID_OPUS);
+ assertIndex(new IndexPoint(0, 0, TEST_DURATION_US));
+ }
+
+ public void testPrepareVorbis() throws ParserException {
+ NonBlockingInputStream testInputStream = new ByteArrayNonBlockingInputStream(
+ createInitializationSegment(1, 0, true, DEFAULT_TIMECODE_SCALE, ID_VORBIS));
+ assertEquals(EXPECTED_INIT_RESULT, extractor.read(testInputStream, sampleHolder));
+ assertAudioFormat(ID_VORBIS);
+ assertIndex(new IndexPoint(0, 0, TEST_DURATION_US));
+ }
+
+ public void testPrepareThreeCuePoints() throws ParserException {
+ NonBlockingInputStream testInputStream = new ByteArrayNonBlockingInputStream(
+ createInitializationSegment(3, 0, true, DEFAULT_TIMECODE_SCALE, ID_VP9));
+ assertEquals(EXPECTED_INIT_RESULT, extractor.read(testInputStream, sampleHolder));
+ assertFormat();
+ assertIndex(
+ new IndexPoint(0, 0, 10000),
+ new IndexPoint(10000, 0, 10000),
+ new IndexPoint(20000, 0, TEST_DURATION_US - 20000));
+ }
+
+ public void testPrepareCustomTimecodeScale() throws ParserException {
+ NonBlockingInputStream testInputStream = new ByteArrayNonBlockingInputStream(
+ createInitializationSegment(3, 0, true, 1000, ID_VP9));
+ assertEquals(EXPECTED_INIT_RESULT, extractor.read(testInputStream, sampleHolder));
+ assertFormat();
+ assertIndex(
+ new IndexPoint(0, 0, 10),
+ new IndexPoint(10, 0, 10),
+ new IndexPoint(20, 0, (TEST_DURATION_US / 1000) - 20));
+ }
+
+ public void testPrepareNoCuePoints() {
+ NonBlockingInputStream testInputStream = new ByteArrayNonBlockingInputStream(
+ createInitializationSegment(0, 0, true, DEFAULT_TIMECODE_SCALE, ID_VP9));
+ try {
+ extractor.read(testInputStream, sampleHolder);
+ fail();
+ } catch (ParserException exception) {
+ assertEquals("Invalid/missing cue points", exception.getMessage());
+ }
+ }
+
+ public void testPrepareInvalidDocType() {
+ NonBlockingInputStream testInputStream = new ByteArrayNonBlockingInputStream(
+ createInitializationSegment(1, 0, false, DEFAULT_TIMECODE_SCALE, ID_VP9));
+ try {
+ extractor.read(testInputStream, sampleHolder);
+ fail();
+ } catch (ParserException exception) {
+ assertEquals("DocType webB not supported", exception.getMessage());
+ }
+ }
+
+ public void testReadSampleKeyframe() throws ParserException {
+ MediaSegment mediaSegment = createMediaSegment(100, 0, 0, true, false, true);
+ byte[] testInputData = joinByteArrays(
+ createInitializationSegment(
+ 1, mediaSegment.clusterBytes.length, true, DEFAULT_TIMECODE_SCALE, ID_VP9),
+ mediaSegment.clusterBytes);
+ NonBlockingInputStream testInputStream = new ByteArrayNonBlockingInputStream(testInputData);
+ assertEquals(EXPECTED_INIT_AND_SAMPLE_RESULT, extractor.read(testInputStream, sampleHolder));
+ assertFormat();
+ assertSample(mediaSegment, 0, true, false);
+ assertEquals(WebmExtractor.RESULT_END_OF_STREAM, extractor.read(testInputStream, sampleHolder));
+ }
+
+ public void testReadBlock() throws ParserException {
+ MediaSegment mediaSegment = createMediaSegment(100, 0, 0, true, false, false);
+ byte[] testInputData = joinByteArrays(
+ createInitializationSegment(
+ 1, mediaSegment.clusterBytes.length, true, DEFAULT_TIMECODE_SCALE, ID_OPUS),
+ mediaSegment.clusterBytes);
+ NonBlockingInputStream testInputStream = new ByteArrayNonBlockingInputStream(testInputData);
+ assertEquals(EXPECTED_INIT_AND_SAMPLE_RESULT, extractor.read(testInputStream, sampleHolder));
+ assertAudioFormat(ID_OPUS);
+ assertSample(mediaSegment, 0, true, false);
+ assertEquals(WebmExtractor.RESULT_END_OF_STREAM, extractor.read(testInputStream, sampleHolder));
+ }
+
+ public void testReadSampleInvisible() throws ParserException {
+ MediaSegment mediaSegment = createMediaSegment(100, 12, 13, false, true, true);
+ byte[] testInputData = joinByteArrays(
+ createInitializationSegment(
+ 1, mediaSegment.clusterBytes.length, true, DEFAULT_TIMECODE_SCALE, ID_VP9),
+ mediaSegment.clusterBytes);
+ NonBlockingInputStream testInputStream = new ByteArrayNonBlockingInputStream(testInputData);
+ assertEquals(EXPECTED_INIT_AND_SAMPLE_RESULT, extractor.read(testInputStream, sampleHolder));
+ assertFormat();
+ assertSample(mediaSegment, 25000, false, true);
+ assertEquals(WebmExtractor.RESULT_END_OF_STREAM, extractor.read(testInputStream, sampleHolder));
+ }
+
+ public void testReadSampleCustomTimescale() throws ParserException {
+ MediaSegment mediaSegment = createMediaSegment(100, 12, 13, false, false, true);
+ byte[] testInputData = joinByteArrays(
+ createInitializationSegment(
+ 1, mediaSegment.clusterBytes.length, true, 1000, ID_VP9),
+ mediaSegment.clusterBytes);
+ NonBlockingInputStream testInputStream = new ByteArrayNonBlockingInputStream(testInputData);
+ assertEquals(EXPECTED_INIT_AND_SAMPLE_RESULT, extractor.read(testInputStream, sampleHolder));
+ assertFormat();
+ assertSample(mediaSegment, 25, false, false);
+ assertEquals(WebmExtractor.RESULT_END_OF_STREAM, extractor.read(testInputStream, sampleHolder));
+ }
+
+ public void testReadSampleNegativeSimpleBlockTimecode() throws ParserException {
+ MediaSegment mediaSegment = createMediaSegment(100, 13, -12, true, true, true);
+ byte[] testInputData = joinByteArrays(
+ createInitializationSegment(
+ 1, mediaSegment.clusterBytes.length, true, DEFAULT_TIMECODE_SCALE, ID_VP9),
+ mediaSegment.clusterBytes);
+ NonBlockingInputStream testInputStream = new ByteArrayNonBlockingInputStream(testInputData);
+ assertEquals(EXPECTED_INIT_AND_SAMPLE_RESULT, extractor.read(testInputStream, sampleHolder));
+ assertFormat();
+ assertSample(mediaSegment, 1000, true, true);
+ assertEquals(WebmExtractor.RESULT_END_OF_STREAM, extractor.read(testInputStream, sampleHolder));
+ }
+
+ private void assertFormat() {
+ MediaFormat format = extractor.getFormat();
+ assertEquals(TEST_WIDTH, format.width);
+ assertEquals(TEST_HEIGHT, format.height);
+ assertEquals(MimeTypes.VIDEO_VP9, format.mimeType);
+ }
+
+ private void assertAudioFormat(int codecId) {
+ MediaFormat format = extractor.getFormat();
+ assertEquals(TEST_CHANNEL_COUNT, format.channelCount);
+ assertEquals(TEST_SAMPLE_RATE, format.sampleRate);
+ if (codecId == ID_OPUS) {
+ assertEquals(MimeTypes.AUDIO_OPUS, format.mimeType);
+ assertEquals(3, format.initializationData.size());
+ assertEquals(TEST_OPUS_CODEC_PRIVATE_SIZE, format.initializationData.get(0).length);
+ assertEquals(TEST_CODEC_DELAY, ByteBuffer.wrap(format.initializationData.get(1)).getLong());
+ assertEquals(TEST_SEEK_PRE_ROLL, ByteBuffer.wrap(format.initializationData.get(2)).getLong());
+ } else if (codecId == ID_VORBIS) {
+ assertEquals(MimeTypes.AUDIO_VORBIS, format.mimeType);
+ assertEquals(2, format.initializationData.size());
+ assertEquals(TEST_VORBIS_INFO_SIZE, format.initializationData.get(0).length);
+ assertEquals(TEST_VORBIS_BOOKS_SIZE, format.initializationData.get(1).length);
+ }
+ }
+
+ private void assertIndex(IndexPoint... indexPoints) {
+ SegmentIndex index = extractor.getIndex();
+ assertEquals(CUES_ELEMENT_BYTE_SIZE + CUE_POINT_ELEMENT_BYTE_SIZE * indexPoints.length,
+ index.sizeBytes);
+ assertEquals(indexPoints.length, index.length);
+ for (int i = 0; i < indexPoints.length; i++) {
+ IndexPoint indexPoint = indexPoints[i];
+ assertEquals(indexPoint.timeUs, index.timesUs[i]);
+ assertEquals(indexPoint.size, index.sizes[i]);
+ assertEquals(indexPoint.durationUs, index.durationsUs[i]);
+ }
+ }
+
+ private void assertSample(
+ MediaSegment mediaSegment, int timeUs, boolean keyframe, boolean invisible) {
+ assertTrue(Arrays.equals(
+ mediaSegment.videoBytes, Arrays.copyOf(sampleHolder.data.array(), sampleHolder.size)));
+ assertEquals(timeUs, sampleHolder.timeUs);
+ assertEquals(keyframe, (sampleHolder.flags & C.SAMPLE_FLAG_SYNC) != 0);
+ assertEquals(invisible, sampleHolder.decodeOnly);
+ }
+
+ private byte[] createInitializationSegment(
+ int cuePoints, int mediaSegmentSize, boolean docTypeIsWebm, int timecodeScale,
+ int codecId) {
+ int initalizationSegmentSize = INFO_ELEMENT_BYTE_SIZE + TRACKS_ELEMENT_BYTE_SIZE
+ + CUES_ELEMENT_BYTE_SIZE + CUE_POINT_ELEMENT_BYTE_SIZE * cuePoints;
+ byte[] tracksElement = null;
+ switch (codecId) {
+ case ID_VP9:
+ tracksElement = createTracksElementWithVideo(true, TEST_WIDTH, TEST_HEIGHT);
+ break;
+ case ID_OPUS:
+ tracksElement = createTracksElementWithOpusAudio(TEST_CHANNEL_COUNT);
+ break;
+ case ID_VORBIS:
+ tracksElement = createTracksElementWithVorbisAudio(TEST_CHANNEL_COUNT);
+ break;
+ }
+ byte[] bytes = joinByteArrays(createEbmlElement(1, docTypeIsWebm, 2),
+ createSegmentElement(initalizationSegmentSize + mediaSegmentSize),
+ createInfoElement(timecodeScale),
+ tracksElement,
+ createCuesElement(CUE_POINT_ELEMENT_BYTE_SIZE * cuePoints));
+ for (int i = 0; i < cuePoints; i++) {
+ bytes = joinByteArrays(bytes, createCuePointElement(10 * i, initalizationSegmentSize));
+ }
+ return bytes;
+ }
+
+ private static MediaSegment createMediaSegment(int videoBytesLength, int clusterTimecode,
+ int blockTimecode, boolean keyframe, boolean invisible, boolean isSimple) {
+ byte[] videoBytes = createVideoBytes(videoBytesLength);
+ byte[] blockBytes;
+ if (isSimple) {
+ blockBytes = createSimpleBlockElement(videoBytes.length, blockTimecode,
+ keyframe, invisible, true);
+ } else {
+ blockBytes = createBlockElement(videoBytes.length, blockTimecode, invisible, true);
+ }
+ byte[] clusterBytes =
+ createClusterElement(blockBytes.length + videoBytes.length, clusterTimecode);
+ return new MediaSegment(joinByteArrays(clusterBytes, blockBytes, videoBytes), videoBytes);
+ }
+
+ private static byte[] joinByteArrays(byte[]... byteArrays) {
+ int length = 0;
+ for (byte[] byteArray : byteArrays) {
+ length += byteArray.length;
+ }
+ byte[] joined = new byte[length];
+ length = 0;
+ for (byte[] byteArray : byteArrays) {
+ System.arraycopy(byteArray, 0, joined, length, byteArray.length);
+ length += byteArray.length;
+ }
+ return joined;
+ }
+
+ private static byte[] createEbmlElement(
+ int ebmlReadVersion, boolean docTypeIsWebm, int docTypeReadVersion) {
+ return createByteArray(
+ 0x1A, 0x45, 0xDF, 0xA3, // EBML
+ 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0F, // size=15
+ 0x42, 0xF7, // EBMLReadVersion
+ 0x81, ebmlReadVersion, // size=1
+ 0x42, 0x82, // DocType
+ 0x84, 0x77, 0x65, 0x62, docTypeIsWebm ? 0x6D : 0x42, // size=4 value=webm/B
+ 0x42, 0x85, // DocTypeReadVersion
+ 0x81, docTypeReadVersion); // size=1
+ }
+
+ private static byte[] createSegmentElement(int size) {
+ byte[] sizeBytes = getIntegerBytes(size);
+ return createByteArray(
+ 0x18, 0x53, 0x80, 0x67, // Segment
+ 0x01, 0x00, 0x00, 0x00, sizeBytes[0], sizeBytes[1], sizeBytes[2], sizeBytes[3]);
+ }
+
+ private static byte[] createInfoElement(int timecodeScale) {
+ byte[] scaleBytes = getIntegerBytes(timecodeScale);
+ return createByteArray(
+ 0x15, 0x49, 0xA9, 0x66, // Info
+ 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x13, // size=19
+ 0x2A, 0xD7, 0xB1, // TimecodeScale
+ 0x84, scaleBytes[0], scaleBytes[1], scaleBytes[2], scaleBytes[3], // size=4
+ 0x44, 0x89, // Duration
+ 0x88, 0x40, 0xC3, 0x60, 0x00, 0x00, 0x00, 0x00, 0x00); // size=8 value=9920.0
+ }
+
+ private static byte[] createTracksElementWithVideo(
+ boolean codecIsVp9, int pixelWidth, int pixelHeight) {
+ byte[] widthBytes = getIntegerBytes(pixelWidth);
+ byte[] heightBytes = getIntegerBytes(pixelHeight);
+ return createByteArray(
+ 0x16, 0x54, 0xAE, 0x6B, // Tracks
+ 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x24, // size=36
+ 0xAE, // TrackEntry
+ 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x1B, // size=27
+ 0x86, // CodecID
+ 0x85, 0x56, 0x5F, 0x56, 0x50, codecIsVp9 ? 0x39 : 0x30, // size=5 value=V_VP9/0
+ 0xE0, // Video
+ 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x08, // size=8
+ 0xB0, // PixelWidth
+ 0x82, widthBytes[2], widthBytes[3], // size=2
+ 0xBA, // PixelHeight
+ 0x82, heightBytes[2], heightBytes[3]); // size=2
+ }
+
+ private static byte[] createTracksElementWithOpusAudio(int channelCount) {
+ byte[] channelCountBytes = getIntegerBytes(channelCount);
+ return createByteArray(
+ 0x16, 0x54, 0xAE, 0x6B, // Tracks
+ 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x39, // size=57
+ 0xAE, // TrackEntry
+ 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x30, // size=48
+ 0x86, // CodecID
+ 0x86, 0x41, 0x5F, 0x4F, 0x50, 0x55, 0x53, // size=6 value=A_OPUS
+ 0x56, 0xAA, // CodecDelay
+ 0x83, 0x63, 0x2E, 0xA0, // size=3 value=6500000
+ 0x56, 0xBB, // SeekPreRoll
+ 0x84, 0x04, 0xC4, 0xB4, 0x00, // size=4 value=80000000
+ 0xE1, // Audio
+ 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0D, // size=13
+ 0x9F, // Channels
+ 0x81, channelCountBytes[3], // size=1
+ 0xB5, // SamplingFrequency
+ 0x88, 0x40, 0xE7, 0x70, 0x00, 0x00, 0x00, 0x00, 0x00, // size=8 value=48000
+ 0x63, 0xA2, // CodecPrivate
+ 0x82, 0x00, 0x00); // size=2
+ }
+
+ private byte[] createTracksElementWithVorbisAudio(int channelCount) {
+ byte[] channelCountBytes = getIntegerBytes(channelCount);
+ byte[] tracksElement = createByteArray(
+ 0x16, 0x54, 0xAE, 0x6B, // Tracks
+ 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x10, 0x9C, // size=4252
+ 0xAE, // TrackEntry
+ 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x10, 0x93, // size=4243 (36+4207)
+ 0x86, // CodecID
+ 0x88, 0x41, 0x5f, 0x56, 0x4f, 0x52, 0x42, 0x49, 0x53, // size=8 value=A_VORBIS
+ 0xE1, // Audio
+ 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0D, // size=13
+ 0x9F, // Channels
+ 0x81, channelCountBytes[3], // size=1
+ 0xB5, // SamplingFrequency
+ 0x88, 0x40, 0xE7, 0x70, 0x00, 0x00, 0x00, 0x00, 0x00, // size=8 value=48000
+ 0x63, 0xA2, // CodecPrivate
+ 0x50, 0x6F); // size=4207
+ byte[] codecPrivate = new byte[4207];
+ try {
+ getInstrumentation().getContext().getResources().getAssets().open(TEST_VORBIS_CODEC_PRIVATE)
+ .read(codecPrivate);
+ } catch (IOException e) {
+ fail(); // should never happen
+ }
+ return joinByteArrays(tracksElement, codecPrivate);
+ }
+
+ private static byte[] createCuesElement(int size) {
+ byte[] sizeBytes = getIntegerBytes(size);
+ return createByteArray(
+ 0x1C, 0x53, 0xBB, 0x6B, // Cues
+ 0x01, 0x00, 0x00, 0x00, sizeBytes[0], sizeBytes[1], sizeBytes[2], sizeBytes[3]); // size=31
+ }
+
+ private static byte[] createCuePointElement(int cueTime, int cueClusterPosition) {
+ byte[] positionBytes = getIntegerBytes(cueClusterPosition);
+ return createByteArray(
+ 0xBB, // CuePoint
+ 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x16, // size=22
+ 0xB3, // CueTime
+ 0x81, cueTime, // size=1
+ 0xB7, // CueTrackPositions
+ 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0A, // size=10
+ 0xF1, // CueClusterPosition
+ 0x88, 0x00, 0x00, 0x00, 0x00, positionBytes[0], positionBytes[1],
+ positionBytes[2], positionBytes[3]); // size=8
+ }
+
+ private static byte[] createClusterElement(int size, int timecode) {
+ byte[] sizeBytes = getIntegerBytes(size);
+ byte[] timeBytes = getIntegerBytes(timecode);
+ return createByteArray(
+ 0x1F, 0x43, 0xB6, 0x75, // Cluster
+ 0x01, 0x00, 0x00, 0x00, sizeBytes[0], sizeBytes[1], sizeBytes[2], sizeBytes[3],
+ 0xE7, // Timecode
+ 0x84, timeBytes[0], timeBytes[1], timeBytes[2], timeBytes[3]); // size=4
+ }
+
+ private static byte[] createSimpleBlockElement(
+ int size, int timecode, boolean keyframe, boolean invisible, boolean noLacing) {
+ byte[] sizeBytes = getIntegerBytes(size + 4);
+ byte[] timeBytes = getIntegerBytes(timecode);
+ byte flags = (byte)
+ ((keyframe ? 0x80 : 0x00) | (invisible ? 0x08 : 0x00) | (noLacing ? 0x00 : 0x06));
+ return createByteArray(
+ 0xA3, // SimpleBlock
+ 0x01, 0x00, 0x00, 0x00, sizeBytes[0], sizeBytes[1], sizeBytes[2], sizeBytes[3],
+ 0x81, // Track number value=1
+ timeBytes[2], timeBytes[3], flags); // Timecode and flags
+ }
+
+ private static byte[] createBlockElement(
+ int size, int timecode, boolean invisible, boolean noLacing) {
+ int blockSize = size + 4;
+ byte[] blockSizeBytes = getIntegerBytes(blockSize);
+ byte[] timeBytes = getIntegerBytes(timecode);
+ int blockElementSize = 1 + 8 + blockSize; // id + size + length of data
+ byte[] sizeBytes = getIntegerBytes(blockElementSize);
+ byte flags = (byte) ((invisible ? 0x08 : 0x00) | (noLacing ? 0x00 : 0x06));
+ return createByteArray(
+ 0xA0, // BlockGroup
+ 0x01, 0x00, 0x00, 0x00, sizeBytes[0], sizeBytes[1], sizeBytes[2], sizeBytes[3],
+ 0xA1, // Block
+ 0x01, 0x00, 0x00, 0x00,
+ blockSizeBytes[0], blockSizeBytes[1], blockSizeBytes[2], blockSizeBytes[3],
+ 0x81, // Track number value=1
+ timeBytes[2], timeBytes[3], flags); // Timecode and flags
+ }
+
+ private static byte[] createVideoBytes(int size) {
+ byte[] videoBytes = new byte[size];
+ for (int i = 0; i < size; i++) {
+ videoBytes[i] = (byte) i;
+ }
+ return videoBytes;
+ }
+
+ private static byte[] getIntegerBytes(int value) {
+ return createByteArray(
+ (value & 0xFF000000) >> 24,
+ (value & 0x00FF0000) >> 16,
+ (value & 0x0000FF00) >> 8,
+ (value & 0x000000FF));
+ }
+
+ private static byte[] createByteArray(int... intArray) {
+ byte[] byteArray = new byte[intArray.length];
+ for (int i = 0; i < byteArray.length; i++) {
+ byteArray[i] = (byte) intArray[i];
+ }
+ return byteArray;
+ }
+
+ /** Used by {@link #createMediaSegment} to return both cluster and video bytes together. */
+ private static final class MediaSegment {
+
+ private final byte[] clusterBytes;
+ private final byte[] videoBytes;
+
+ private MediaSegment(byte[] clusterBytes, byte[] videoBytes) {
+ this.clusterBytes = clusterBytes;
+ this.videoBytes = videoBytes;
+ }
+
+ }
+
+ /** Used by {@link #assertIndex(IndexPoint...)} to validate index elements. */
+ private static final class IndexPoint {
+
+ private final long timeUs;
+ private final int size;
+ private final long durationUs;
+
+ private IndexPoint(long timeUs, int size, long durationUs) {
+ this.timeUs = timeUs;
+ this.size = size;
+ this.durationUs = durationUs;
+ }
+
+ }
+
+}
diff --git a/library/src/test/java/com/google/android/exoplayer/dash/DashChunkSourceTest.java b/library/src/test/java/com/google/android/exoplayer/dash/DashChunkSourceTest.java
new file mode 100644
index 0000000000..d4e116e833
--- /dev/null
+++ b/library/src/test/java/com/google/android/exoplayer/dash/DashChunkSourceTest.java
@@ -0,0 +1,49 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.google.android.exoplayer.dash;
+
+import com.google.android.exoplayer.MediaFormat;
+import com.google.android.exoplayer.chunk.Format;
+import com.google.android.exoplayer.dash.mpd.Representation;
+import com.google.android.exoplayer.dash.mpd.SegmentBase.SingleSegmentBase;
+
+import junit.framework.TestCase;
+
+/**
+ * Tests {@link DashChunkSource}.
+ */
+public class DashChunkSourceTest extends TestCase {
+
+ public void testMaxVideoDimensions() {
+ SingleSegmentBase segmentBase1 = new SingleSegmentBase("https://example.com/1.mp4");
+ Format format1 = new Format("1", "video/mp4", 100, 200, -1, -1, 1000);
+ Representation representation1 =
+ Representation.newInstance(0, 0, null, 0, format1, segmentBase1);
+
+ SingleSegmentBase segmentBase2 = new SingleSegmentBase("https://example.com/2.mp4");
+ Format format2 = new Format("2", "video/mp4", 400, 50, -1, -1, 1000);
+ Representation representation2 =
+ Representation.newInstance(0, 0, null, 0, format2, segmentBase2);
+
+ DashChunkSource chunkSource = new DashChunkSource(null, null, representation1, representation2);
+ MediaFormat out = MediaFormat.createVideoFormat("video/h264", 1, 1, 1, 1, null);
+ chunkSource.getMaxVideoDimensions(out);
+
+ assertEquals(400, out.getMaxVideoWidth());
+ assertEquals(200, out.getMaxVideoHeight());
+ }
+
+}
diff --git a/library/src/test/java/com/google/android/exoplayer/dash/mpd/MediaPresentationDescriptionParserTest.java b/library/src/test/java/com/google/android/exoplayer/dash/mpd/MediaPresentationDescriptionParserTest.java
new file mode 100644
index 0000000000..7aa65564a0
--- /dev/null
+++ b/library/src/test/java/com/google/android/exoplayer/dash/mpd/MediaPresentationDescriptionParserTest.java
@@ -0,0 +1,38 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.google.android.exoplayer.dash.mpd;
+
+import android.test.InstrumentationTestCase;
+
+import java.io.IOException;
+import java.io.InputStream;
+
+/**
+ * Unit tests for {@link MediaPresentationDescriptionParser}.
+ */
+public class MediaPresentationDescriptionParserTest extends InstrumentationTestCase {
+
+ private static final String SAMPLE_MPD_1 = "dash/sample_mpd_1";
+
+ public void testParseMediaPresentationDescription() throws IOException {
+ MediaPresentationDescriptionParser parser = new MediaPresentationDescriptionParser();
+ InputStream inputStream =
+ getInstrumentation().getContext().getResources().getAssets().open(SAMPLE_MPD_1);
+ // Simple test to ensure that the sample manifest parses without throwing any exceptions.
+ parser.parse("https://example.com/test.mpd", inputStream);
+ }
+
+}
diff --git a/library/src/test/java/com/google/android/exoplayer/dash/mpd/RangedUriTest.java b/library/src/test/java/com/google/android/exoplayer/dash/mpd/RangedUriTest.java
new file mode 100644
index 0000000000..52d5c1dd07
--- /dev/null
+++ b/library/src/test/java/com/google/android/exoplayer/dash/mpd/RangedUriTest.java
@@ -0,0 +1,78 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.google.android.exoplayer.dash.mpd;
+
+import junit.framework.TestCase;
+
+/**
+ * Unit test for {@link RangedUri}.
+ */
+public class RangedUriTest extends TestCase {
+
+ private static final String FULL_URI = "http://www.test.com/path/file.ext";
+
+ public void testMerge() {
+ RangedUri rangeA = new RangedUri(null, FULL_URI, 0, 10);
+ RangedUri rangeB = new RangedUri(null, FULL_URI, 10, 10);
+ RangedUri expected = new RangedUri(null, FULL_URI, 0, 20);
+ assertMerge(rangeA, rangeB, expected);
+ }
+
+ public void testMergeUnbounded() {
+ RangedUri rangeA = new RangedUri(null, FULL_URI, 0, 10);
+ RangedUri rangeB = new RangedUri(null, FULL_URI, 10, -1);
+ RangedUri expected = new RangedUri(null, FULL_URI, 0, -1);
+ assertMerge(rangeA, rangeB, expected);
+ }
+
+ public void testNonMerge() {
+ // A and B do not overlap, so should not merge
+ RangedUri rangeA = new RangedUri(null, FULL_URI, 0, 10);
+ RangedUri rangeB = new RangedUri(null, FULL_URI, 11, 10);
+ assertNonMerge(rangeA, rangeB);
+
+ // A and B do not overlap, so should not merge
+ rangeA = new RangedUri(null, FULL_URI, 0, 10);
+ rangeB = new RangedUri(null, FULL_URI, 11, -1);
+ assertNonMerge(rangeA, rangeB);
+
+ // A and B are bounded but overlap, so should not merge
+ rangeA = new RangedUri(null, FULL_URI, 0, 11);
+ rangeB = new RangedUri(null, FULL_URI, 10, 10);
+ assertNonMerge(rangeA, rangeB);
+
+ // A and B overlap due to unboundedness, so should not merge
+ rangeA = new RangedUri(null, FULL_URI, 0, -1);
+ rangeB = new RangedUri(null, FULL_URI, 10, -1);
+ assertNonMerge(rangeA, rangeB);
+
+ }
+
+ private void assertMerge(RangedUri rangeA, RangedUri rangeB, RangedUri expected) {
+ RangedUri merged = rangeA.attemptMerge(rangeB);
+ assertEquals(expected, merged);
+ merged = rangeB.attemptMerge(rangeA);
+ assertEquals(expected, merged);
+ }
+
+ private void assertNonMerge(RangedUri rangeA, RangedUri rangeB) {
+ RangedUri merged = rangeA.attemptMerge(rangeB);
+ assertNull(merged);
+ merged = rangeB.attemptMerge(rangeA);
+ assertNull(merged);
+ }
+
+}
diff --git a/library/src/test/java/com/google/android/exoplayer/dash/mpd/RepresentationTest.java b/library/src/test/java/com/google/android/exoplayer/dash/mpd/RepresentationTest.java
new file mode 100644
index 0000000000..19d2226014
--- /dev/null
+++ b/library/src/test/java/com/google/android/exoplayer/dash/mpd/RepresentationTest.java
@@ -0,0 +1,42 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.google.android.exoplayer.dash.mpd;
+
+import com.google.android.exoplayer.chunk.Format;
+import com.google.android.exoplayer.dash.mpd.SegmentBase.SingleSegmentBase;
+import com.google.android.exoplayer.util.MimeTypes;
+
+import junit.framework.TestCase;
+
+/**
+ * Unit test for {@link Representation}.
+ */
+public class RepresentationTest extends TestCase {
+
+ public void testGetCacheKey() {
+ String uri = "http://www.google.com";
+ SegmentBase base = new SingleSegmentBase(new RangedUri(uri, null, 0, 1), 1, 0, uri, 1, 1);
+ Format format = new Format("0", MimeTypes.VIDEO_MP4, 1920, 1080, 0, 0, 2500000);
+ Representation representation = Representation.newInstance(-1, -1, "test_stream_1", 3,
+ format, base);
+ assertEquals("test_stream_1.0.3", representation.getCacheKey());
+
+ format = new Format("150", MimeTypes.VIDEO_MP4, 1920, 1080, 0, 0, 2500000);
+ representation = Representation.newInstance(-1, -1, "test_stream_1", -1, format, base);
+ assertEquals("test_stream_1.150.-1", representation.getCacheKey());
+ }
+
+}
diff --git a/library/src/test/java/com/google/android/exoplayer/dash/mpd/UrlTemplateTest.java b/library/src/test/java/com/google/android/exoplayer/dash/mpd/UrlTemplateTest.java
new file mode 100644
index 0000000000..55084cc4c9
--- /dev/null
+++ b/library/src/test/java/com/google/android/exoplayer/dash/mpd/UrlTemplateTest.java
@@ -0,0 +1,66 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.google.android.exoplayer.dash.mpd;
+
+import junit.framework.TestCase;
+
+/**
+ * Unit test for {@link UrlTemplate}.
+ */
+public class UrlTemplateTest extends TestCase {
+
+ public void testRealExamples() {
+ String template = "QualityLevels($Bandwidth$)/Fragments(video=$Time$,format=mpd-time-csf)";
+ UrlTemplate urlTemplate = UrlTemplate.compile(template);
+ String url = urlTemplate.buildUri("abc1", 10, 650000, 5000);
+ assertEquals("QualityLevels(650000)/Fragments(video=5000,format=mpd-time-csf)", url);
+
+ template = "$RepresentationID$/$Number$";
+ urlTemplate = UrlTemplate.compile(template);
+ url = urlTemplate.buildUri("abc1", 10, 650000, 5000);
+ assertEquals("abc1/10", url);
+
+ template = "chunk_ctvideo_cfm4s_rid$RepresentationID$_cn$Number$_w2073857842_mpd.m4s";
+ urlTemplate = UrlTemplate.compile(template);
+ url = urlTemplate.buildUri("abc1", 10, 650000, 5000);
+ assertEquals("chunk_ctvideo_cfm4s_ridabc1_cn10_w2073857842_mpd.m4s", url);
+ }
+
+ public void testFull() {
+ String template = "$Bandwidth$_a_$RepresentationID$_b_$Time$_c_$Number$";
+ UrlTemplate urlTemplate = UrlTemplate.compile(template);
+ String url = urlTemplate.buildUri("abc1", 10, 650000, 5000);
+ assertEquals("650000_a_abc1_b_5000_c_10", url);
+ }
+
+ public void testFullWithDollarEscaping() {
+ String template = "$$$Bandwidth$$$_a$$_$RepresentationID$_b_$Time$_c_$Number$$$";
+ UrlTemplate urlTemplate = UrlTemplate.compile(template);
+ String url = urlTemplate.buildUri("abc1", 10, 650000, 5000);
+ assertEquals("$650000$_a$_abc1_b_5000_c_10$", url);
+ }
+
+ public void testInvalidSubstitution() {
+ String template = "$IllegalId$";
+ try {
+ UrlTemplate.compile(template);
+ assertTrue(false);
+ } catch (IllegalArgumentException e) {
+ // Expected.
+ }
+ }
+
+}
diff --git a/library/src/test/java/com/google/android/exoplayer/hls/HlsMasterPlaylistParserTest.java b/library/src/test/java/com/google/android/exoplayer/hls/HlsMasterPlaylistParserTest.java
new file mode 100644
index 0000000000..bd5126160a
--- /dev/null
+++ b/library/src/test/java/com/google/android/exoplayer/hls/HlsMasterPlaylistParserTest.java
@@ -0,0 +1,108 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.google.android.exoplayer.hls;
+
+import com.google.android.exoplayer.C;
+
+import junit.framework.TestCase;
+
+import java.io.ByteArrayInputStream;
+import java.io.IOException;
+import java.nio.charset.Charset;
+import java.util.List;
+
+/**
+ * Test for {@link HlsMasterPlaylistParserTest}
+ */
+public class HlsMasterPlaylistParserTest extends TestCase {
+
+ public void testParseMasterPlaylist() {
+ String playlistUrl = "https://example.com/test.m3u8";
+ String playlistString = "#EXTM3U\n"
+ + "\n"
+ + "#EXT-X-STREAM-INF:BANDWIDTH=1280000,CODECS=\"mp4a.40.2,avc1.66.30\",RESOLUTION=304x128\n"
+ + "http://example.com/low.m3u8\n"
+ + "\n"
+ + "#EXT-X-STREAM-INF:BANDWIDTH=1280000,CODECS=\"mp4a.40.2 , avc1.66.30 \"\n"
+ + "http://example.com/spaces_in_codecs.m3u8\n"
+ + "\n"
+ + "#EXT-X-STREAM-INF:BANDWIDTH=2560000,RESOLUTION=384x160\n"
+ + "http://example.com/mid.m3u8\n"
+ + "\n"
+ + "#EXT-X-STREAM-INF:BANDWIDTH=7680000\n"
+ + "http://example.com/hi.m3u8\n"
+ + "\n"
+ + "#EXT-X-STREAM-INF:BANDWIDTH=65000,CODECS=\"mp4a.40.5\"\n"
+ + "http://example.com/audio-only.m3u8";
+ ByteArrayInputStream inputStream = new ByteArrayInputStream(
+ playlistString.getBytes(Charset.forName(C.UTF8_NAME)));
+ try {
+ HlsPlaylist playlist = new HlsPlaylistParser().parse(playlistUrl, inputStream);
+ assertNotNull(playlist);
+ assertEquals(HlsPlaylist.TYPE_MASTER, playlist.type);
+
+ HlsMasterPlaylist masterPlaylist = (HlsMasterPlaylist) playlist;
+
+ List 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());
+ }
+ }
+
+}
diff --git a/library/src/test/java/com/google/android/exoplayer/hls/HlsMediaPlaylistParserTest.java b/library/src/test/java/com/google/android/exoplayer/hls/HlsMediaPlaylistParserTest.java
new file mode 100644
index 0000000000..7d63db1efe
--- /dev/null
+++ b/library/src/test/java/com/google/android/exoplayer/hls/HlsMediaPlaylistParserTest.java
@@ -0,0 +1,135 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.google.android.exoplayer.hls;
+
+import com.google.android.exoplayer.C;
+
+import junit.framework.TestCase;
+
+import java.io.ByteArrayInputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.nio.charset.Charset;
+import java.util.List;
+import java.util.Locale;
+
+/**
+ * Test for {@link HlsMediaPlaylistParserTest}
+ */
+public class HlsMediaPlaylistParserTest extends TestCase {
+
+ public void testParseMediaPlaylist() {
+ String playlistUrl = "https://example.com/test.m3u8";
+ String playlistString = "#EXTM3U\n"
+ + "#EXT-X-VERSION:3\n"
+ + "#EXT-X-TARGETDURATION:8\n"
+ + "#EXT-X-MEDIA-SEQUENCE:2679\n"
+ + "#EXT-X-ALLOW-CACHE:YES\n"
+ + "\n"
+ + "#EXTINF:7.975,\n"
+ + "#EXT-X-BYTERANGE:51370@0\n"
+ + "https://priv.example.com/fileSequence2679.ts\n"
+ + "\n"
+ + "#EXT-X-KEY:METHOD=AES-128,URI=\"https://priv.example.com/key.php?r=2680\",IV=0x1566B\n"
+ + "#EXTINF:7.975,\n"
+ + "#EXT-X-BYTERANGE:51501@51370\n"
+ + "https://priv.example.com/fileSequence2680.ts\n"
+ + "\n"
+ + "#EXT-X-KEY:METHOD=NONE\n"
+ + "#EXTINF:7.941,\n"
+ + "#EXT-X-BYTERANGE:51501\n" // @102871
+ + "https://priv.example.com/fileSequence2681.ts\n"
+ + "\n"
+ + "#EXT-X-DISCONTINUITY\n"
+ + "#EXT-X-KEY:METHOD=AES-128,URI=\"https://priv.example.com/key.php?r=2682\"\n"
+ + "#EXTINF:7.975,\n"
+ + "#EXT-X-BYTERANGE:51740\n" // @154372
+ + "https://priv.example.com/fileSequence2682.ts\n"
+ + "\n"
+ + "#EXTINF:7.975,\n"
+ + "https://priv.example.com/fileSequence2683.ts\n"
+ + "#EXT-X-ENDLIST";
+ InputStream inputStream = new ByteArrayInputStream(
+ playlistString.getBytes(Charset.forName(C.UTF8_NAME)));
+ try {
+ HlsPlaylist playlist = new HlsPlaylistParser().parse(playlistUrl, inputStream);
+ assertNotNull(playlist);
+ assertEquals(HlsPlaylist.TYPE_MEDIA, playlist.type);
+
+ HlsMediaPlaylist mediaPlaylist = (HlsMediaPlaylist) playlist;
+
+ assertEquals(2679, mediaPlaylist.mediaSequence);
+ assertEquals(8, mediaPlaylist.targetDurationSecs);
+ assertEquals(3, mediaPlaylist.version);
+ assertEquals(false, mediaPlaylist.live);
+ List 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());
+ }
+ }
+
+}
diff --git a/library/src/test/java/com/google/android/exoplayer/metadata/Id3ParserTest.java b/library/src/test/java/com/google/android/exoplayer/metadata/Id3ParserTest.java
new file mode 100644
index 0000000000..1dead0a139
--- /dev/null
+++ b/library/src/test/java/com/google/android/exoplayer/metadata/Id3ParserTest.java
@@ -0,0 +1,46 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.google.android.exoplayer.metadata;
+
+import junit.framework.TestCase;
+
+import java.util.Map;
+
+/**
+ * Test for {@link Id3Parser}
+ */
+public class Id3ParserTest extends TestCase {
+
+ public void testParseTxxxFrames() {
+ byte[] rawId3 = new byte[] { 73, 68, 51, 4, 0, 0, 0, 0, 0, 41, 84, 88, 88, 88, 0, 0, 0, 31,
+ 0, 0, 3, 0, 109, 100, 105, 97, 108, 111, 103, 95, 86, 73, 78, 68, 73, 67, 79, 49, 53, 50,
+ 55, 54, 54, 52, 95, 115, 116, 97, 114, 116, 0 };
+
+ Id3Parser parser = new Id3Parser();
+ try {
+ Map 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());
+ }
+ }
+
+}
diff --git a/library/src/test/java/com/google/android/exoplayer/mp4/Mp4UtilTest.java b/library/src/test/java/com/google/android/exoplayer/mp4/Mp4UtilTest.java
new file mode 100644
index 0000000000..fe304b14b6
--- /dev/null
+++ b/library/src/test/java/com/google/android/exoplayer/mp4/Mp4UtilTest.java
@@ -0,0 +1,133 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.google.android.exoplayer.mp4;
+
+import junit.framework.TestCase;
+
+import java.util.Arrays;
+
+/**
+ * Tests for {@link Mp4Util}.
+ */
+public class Mp4UtilTest extends TestCase {
+
+ private static final int TEST_PARTIAL_NAL_POSITION = 4;
+ private static final int TEST_NAL_POSITION = 10;
+
+ public void testFindNalUnit() {
+ byte[] data = buildTestData();
+
+ // Should find NAL unit.
+ int result = Mp4Util.findNalUnit(data, 0, data.length);
+ assertEquals(TEST_NAL_POSITION, result);
+ // Should find NAL unit whose prefix ends one byte before the limit.
+ result = Mp4Util.findNalUnit(data, 0, TEST_NAL_POSITION + 4);
+ assertEquals(TEST_NAL_POSITION, result);
+ // Shouldn't find NAL unit whose prefix ends at the limit (since the limit is exclusive).
+ result = Mp4Util.findNalUnit(data, 0, TEST_NAL_POSITION + 3);
+ assertEquals(TEST_NAL_POSITION + 3, result);
+ // Should find NAL unit whose prefix starts at the offset.
+ result = Mp4Util.findNalUnit(data, TEST_NAL_POSITION, data.length);
+ assertEquals(TEST_NAL_POSITION, result);
+ // Shouldn't find NAL unit whose prefix starts one byte past the offset.
+ result = Mp4Util.findNalUnit(data, TEST_NAL_POSITION + 1, data.length);
+ assertEquals(data.length, result);
+ }
+
+ public void testFindNalUnitWithPrefix() {
+ byte[] data = buildTestData();
+
+ // First byte of NAL unit in data1, rest in data2.
+ boolean[] prefixFlags = new boolean[3];
+ byte[] data1 = Arrays.copyOfRange(data, 0, TEST_NAL_POSITION + 1);
+ byte[] data2 = Arrays.copyOfRange(data, TEST_NAL_POSITION + 1, data.length);
+ int result = Mp4Util.findNalUnit(data1, 0, data1.length, prefixFlags);
+ assertEquals(data1.length, result);
+ result = Mp4Util.findNalUnit(data2, 0, data2.length, prefixFlags);
+ assertEquals(-1, result);
+ assertPrefixFlagsCleared(prefixFlags);
+
+ // First three bytes of NAL unit in data1, rest in data2.
+ prefixFlags = new boolean[3];
+ data1 = Arrays.copyOfRange(data, 0, TEST_NAL_POSITION + 3);
+ data2 = Arrays.copyOfRange(data, TEST_NAL_POSITION + 3, data.length);
+ result = Mp4Util.findNalUnit(data1, 0, data1.length, prefixFlags);
+ assertEquals(data1.length, result);
+ result = Mp4Util.findNalUnit(data2, 0, data2.length, prefixFlags);
+ assertEquals(-3, result);
+ assertPrefixFlagsCleared(prefixFlags);
+
+ // First byte of NAL unit in data1, second byte in data2, rest in data3.
+ prefixFlags = new boolean[3];
+ data1 = Arrays.copyOfRange(data, 0, TEST_NAL_POSITION + 1);
+ data2 = Arrays.copyOfRange(data, TEST_NAL_POSITION + 1, TEST_NAL_POSITION + 2);
+ byte[] data3 = Arrays.copyOfRange(data, TEST_NAL_POSITION + 2, data.length);
+ result = Mp4Util.findNalUnit(data1, 0, data1.length, prefixFlags);
+ assertEquals(data1.length, result);
+ result = Mp4Util.findNalUnit(data2, 0, data2.length, prefixFlags);
+ assertEquals(data2.length, result);
+ result = Mp4Util.findNalUnit(data3, 0, data3.length, prefixFlags);
+ assertEquals(-2, result);
+ assertPrefixFlagsCleared(prefixFlags);
+
+ // NAL unit split with one byte in four arrays.
+ prefixFlags = new boolean[3];
+ data1 = Arrays.copyOfRange(data, 0, TEST_NAL_POSITION + 1);
+ data2 = Arrays.copyOfRange(data, TEST_NAL_POSITION + 1, TEST_NAL_POSITION + 2);
+ data3 = Arrays.copyOfRange(data, TEST_NAL_POSITION + 2, TEST_NAL_POSITION + 3);
+ byte[] data4 = Arrays.copyOfRange(data, TEST_NAL_POSITION + 2, data.length);
+ result = Mp4Util.findNalUnit(data1, 0, data1.length, prefixFlags);
+ assertEquals(data1.length, result);
+ result = Mp4Util.findNalUnit(data2, 0, data2.length, prefixFlags);
+ assertEquals(data2.length, result);
+ result = Mp4Util.findNalUnit(data3, 0, data3.length, prefixFlags);
+ assertEquals(data3.length, result);
+ result = Mp4Util.findNalUnit(data4, 0, data4.length, prefixFlags);
+ assertEquals(-3, result);
+ assertPrefixFlagsCleared(prefixFlags);
+
+ // NAL unit entirely in data2. data1 ends with partial prefix.
+ prefixFlags = new boolean[3];
+ data1 = Arrays.copyOfRange(data, 0, TEST_PARTIAL_NAL_POSITION + 2);
+ data2 = Arrays.copyOfRange(data, TEST_PARTIAL_NAL_POSITION + 2, data.length);
+ result = Mp4Util.findNalUnit(data1, 0, data1.length, prefixFlags);
+ assertEquals(data1.length, result);
+ result = Mp4Util.findNalUnit(data2, 0, data2.length, prefixFlags);
+ assertEquals(4, result);
+ assertPrefixFlagsCleared(prefixFlags);
+ }
+
+ private static byte[] buildTestData() {
+ byte[] data = new byte[20];
+ for (int i = 0; i < data.length; i++) {
+ data[i] = (byte) 0xFF;
+ }
+ // Insert an incomplete NAL unit start code.
+ data[TEST_PARTIAL_NAL_POSITION] = 0;
+ data[TEST_PARTIAL_NAL_POSITION + 1] = 0;
+ // Insert a complete NAL unit start code.
+ data[TEST_NAL_POSITION] = 0;
+ data[TEST_NAL_POSITION + 1] = 0;
+ data[TEST_NAL_POSITION + 2] = 1;
+ data[TEST_NAL_POSITION + 3] = 5;
+ return data;
+ }
+
+ private static void assertPrefixFlagsCleared(boolean[] flags) {
+ assertEquals(false, flags[0] || flags[1] || flags[2]);
+ }
+
+}
diff --git a/library/src/test/java/com/google/android/exoplayer/testutil/Util.java b/library/src/test/java/com/google/android/exoplayer/testutil/Util.java
new file mode 100644
index 0000000000..5f86b51829
--- /dev/null
+++ b/library/src/test/java/com/google/android/exoplayer/testutil/Util.java
@@ -0,0 +1,38 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.google.android.exoplayer.testutil;
+
+import java.util.Random;
+
+/**
+ * Utility methods for tests.
+ */
+public class Util {
+
+ private Util() {}
+
+ public static byte[] buildTestData(int length) {
+ return buildTestData(length, length);
+ }
+
+ public static byte[] buildTestData(int length, int seed) {
+ Random random = new Random(seed);
+ byte[] source = new byte[length];
+ random.nextBytes(source);
+ return source;
+ }
+
+}
diff --git a/library/src/test/java/com/google/android/exoplayer/text/webvtt/WebvttParserTest.java b/library/src/test/java/com/google/android/exoplayer/text/webvtt/WebvttParserTest.java
new file mode 100644
index 0000000000..426c5152df
--- /dev/null
+++ b/library/src/test/java/com/google/android/exoplayer/text/webvtt/WebvttParserTest.java
@@ -0,0 +1,134 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.google.android.exoplayer.text.webvtt;
+
+import com.google.android.exoplayer.C;
+
+import android.test.InstrumentationTestCase;
+
+import java.io.IOException;
+import java.io.InputStream;
+
+/**
+ * Unit test for {@link WebvttParser}.
+ */
+public class WebvttParserTest extends InstrumentationTestCase {
+
+ private static final String TYPICAL_WEBVTT_FILE = "webvtt/typical";
+ private static final String TYPICAL_WITH_IDS_WEBVTT_FILE = "webvtt/typical_with_identifiers";
+ private static final String TYPICAL_WITH_TAGS_WEBVTT_FILE = "webvtt/typical_with_tags";
+ private static final String EMPTY_WEBVTT_FILE = "webvtt/empty";
+
+ public void testParseNullWebvttFile() throws IOException {
+ WebvttParser parser = new WebvttParser();
+ InputStream inputStream =
+ getInstrumentation().getContext().getResources().getAssets().open(EMPTY_WEBVTT_FILE);
+
+ try {
+ parser.parse(inputStream, C.UTF8_NAME, 0);
+ fail("Expected IOException");
+ } catch (IOException expected) {
+ // Do nothing.
+ }
+ }
+
+ public void testParseTypicalWebvttFile() throws IOException {
+ WebvttParser parser = new WebvttParser();
+ InputStream inputStream =
+ getInstrumentation().getContext().getResources().getAssets().open(TYPICAL_WEBVTT_FILE);
+ WebvttSubtitle subtitle = parser.parse(inputStream, C.UTF8_NAME, 0);
+
+ // test start time and event count
+ long startTimeUs = 5000000;
+ assertEquals(startTimeUs, subtitle.getStartTime());
+ assertEquals(4, subtitle.getEventTimeCount());
+
+ // test first cue
+ assertEquals(startTimeUs, subtitle.getEventTime(0));
+ assertEquals("This is the first subtitle.",
+ subtitle.getText(subtitle.getEventTime(0)));
+ assertEquals(startTimeUs + 1234000, subtitle.getEventTime(1));
+
+ // test second cue
+ assertEquals(startTimeUs + 2345000, subtitle.getEventTime(2));
+ assertEquals("This is the second subtitle.",
+ subtitle.getText(subtitle.getEventTime(2)));
+ assertEquals(startTimeUs + 3456000, subtitle.getEventTime(3));
+ }
+
+ public void testParseTypicalWithIdsWebvttFile() throws IOException {
+ WebvttParser parser = new WebvttParser();
+ InputStream inputStream =
+ getInstrumentation().getContext().getResources().getAssets()
+ .open(TYPICAL_WITH_IDS_WEBVTT_FILE);
+ WebvttSubtitle subtitle = parser.parse(inputStream, C.UTF8_NAME, 0);
+
+ // test start time and event count
+ long startTimeUs = 5000000;
+ assertEquals(startTimeUs, subtitle.getStartTime());
+ assertEquals(4, subtitle.getEventTimeCount());
+
+ // test first cue
+ assertEquals(startTimeUs, subtitle.getEventTime(0));
+ assertEquals("This is the first subtitle.",
+ subtitle.getText(subtitle.getEventTime(0)));
+ assertEquals(startTimeUs + 1234000, subtitle.getEventTime(1));
+
+ // test second cue
+ assertEquals(startTimeUs + 2345000, subtitle.getEventTime(2));
+ assertEquals("This is the second subtitle.",
+ subtitle.getText(subtitle.getEventTime(2)));
+ assertEquals(startTimeUs + 3456000, subtitle.getEventTime(3));
+ }
+
+ public void testParseTypicalWithTagsWebvttFile() throws IOException {
+ WebvttParser parser = new WebvttParser();
+ InputStream inputStream =
+ getInstrumentation().getContext().getResources().getAssets()
+ .open(TYPICAL_WITH_TAGS_WEBVTT_FILE);
+ WebvttSubtitle subtitle = parser.parse(inputStream, C.UTF8_NAME, 0);
+
+ // test start time and event count
+ long startTimeUs = 5000000;
+ assertEquals(startTimeUs, subtitle.getStartTime());
+ assertEquals(8, subtitle.getEventTimeCount());
+
+ // test first cue
+ assertEquals(startTimeUs, subtitle.getEventTime(0));
+ assertEquals("This is the first subtitle.",
+ subtitle.getText(subtitle.getEventTime(0)));
+ assertEquals(startTimeUs + 1234000, subtitle.getEventTime(1));
+
+ // test second cue
+ assertEquals(startTimeUs + 2345000, subtitle.getEventTime(2));
+ assertEquals("This is the second subtitle.",
+ subtitle.getText(subtitle.getEventTime(2)));
+ assertEquals(startTimeUs + 3456000, subtitle.getEventTime(3));
+
+ // test third cue
+ assertEquals(startTimeUs + 4000000, subtitle.getEventTime(4));
+ assertEquals("This is the third subtitle.",
+ subtitle.getText(subtitle.getEventTime(4)));
+ assertEquals(startTimeUs + 5000000, subtitle.getEventTime(5));
+
+ // test fourth cue
+ assertEquals(startTimeUs + 6000000, subtitle.getEventTime(6));
+ assertEquals("This is the &subtitle.",
+ subtitle.getText(subtitle.getEventTime(6)));
+ assertEquals(startTimeUs + 7000000, subtitle.getEventTime(7));
+ }
+
+}
diff --git a/library/src/test/java/com/google/android/exoplayer/text/webvtt/WebvttSubtitleTest.java b/library/src/test/java/com/google/android/exoplayer/text/webvtt/WebvttSubtitleTest.java
new file mode 100644
index 0000000000..e95482f0fb
--- /dev/null
+++ b/library/src/test/java/com/google/android/exoplayer/text/webvtt/WebvttSubtitleTest.java
@@ -0,0 +1,204 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.google.android.exoplayer.text.webvtt;
+
+import junit.framework.TestCase;
+
+/**
+ * Unit test for {@link WebvttSubtitle}.
+ */
+public class WebvttSubtitleTest extends TestCase {
+
+ private static final String FIRST_SUBTITLE_STRING = "This is the first subtitle.";
+ private static final String SECOND_SUBTITLE_STRING = "This is the second subtitle.";
+ private static final String FIRST_AND_SECOND_SUBTITLE_STRING =
+ FIRST_SUBTITLE_STRING + SECOND_SUBTITLE_STRING;
+
+ private WebvttSubtitle emptySubtitle = new WebvttSubtitle(new String[] {}, 0, new long[] {});
+
+ private WebvttSubtitle simpleSubtitle = new WebvttSubtitle(
+ new String[] {FIRST_SUBTITLE_STRING, SECOND_SUBTITLE_STRING}, 0,
+ new long[] {1000000, 2000000, 3000000, 4000000});
+
+ private WebvttSubtitle overlappingSubtitle = new WebvttSubtitle(
+ new String[] {FIRST_SUBTITLE_STRING, SECOND_SUBTITLE_STRING}, 0,
+ new long[] {1000000, 3000000, 2000000, 4000000});
+
+ private WebvttSubtitle nestedSubtitle = new WebvttSubtitle(
+ new String[] {FIRST_SUBTITLE_STRING, SECOND_SUBTITLE_STRING}, 0,
+ new long[] {1000000, 4000000, 2000000, 3000000});
+
+ public void testEventCount() {
+ assertEquals(0, emptySubtitle.getEventTimeCount());
+ assertEquals(4, simpleSubtitle.getEventTimeCount());
+ assertEquals(4, overlappingSubtitle.getEventTimeCount());
+ assertEquals(4, nestedSubtitle.getEventTimeCount());
+ }
+
+ public void testStartTime() {
+ assertEquals(0, emptySubtitle.getStartTime());
+ assertEquals(0, simpleSubtitle.getStartTime());
+ assertEquals(0, overlappingSubtitle.getStartTime());
+ assertEquals(0, nestedSubtitle.getStartTime());
+ }
+
+ public void testLastEventTime() {
+ assertEquals(-1, emptySubtitle.getLastEventTime());
+ assertEquals(4000000, simpleSubtitle.getLastEventTime());
+ assertEquals(4000000, overlappingSubtitle.getLastEventTime());
+ assertEquals(4000000, nestedSubtitle.getLastEventTime());
+ }
+
+ public void testSimpleSubtitleEventTimes() {
+ testSubtitleEventTimesHelper(simpleSubtitle);
+ }
+
+ public void testSimpleSubtitleEventIndices() {
+ testSubtitleEventIndicesHelper(simpleSubtitle);
+ }
+
+ public void testSimpleSubtitleText() {
+ // Test before first subtitle
+ assertNull(simpleSubtitle.getText(0));
+ assertNull(simpleSubtitle.getText(500000));
+ assertNull(simpleSubtitle.getText(999999));
+
+ // Test first subtitle
+ assertEquals(FIRST_SUBTITLE_STRING, simpleSubtitle.getText(1000000));
+ assertEquals(FIRST_SUBTITLE_STRING, simpleSubtitle.getText(1500000));
+ assertEquals(FIRST_SUBTITLE_STRING, simpleSubtitle.getText(1999999));
+
+ // Test after first subtitle, before second subtitle
+ assertNull(simpleSubtitle.getText(2000000));
+ assertNull(simpleSubtitle.getText(2500000));
+ assertNull(simpleSubtitle.getText(2999999));
+
+ // Test second subtitle
+ assertEquals(SECOND_SUBTITLE_STRING, simpleSubtitle.getText(3000000));
+ assertEquals(SECOND_SUBTITLE_STRING, simpleSubtitle.getText(3500000));
+ assertEquals(SECOND_SUBTITLE_STRING, simpleSubtitle.getText(3999999));
+
+ // Test after second subtitle
+ assertNull(simpleSubtitle.getText(4000000));
+ assertNull(simpleSubtitle.getText(4500000));
+ assertNull(simpleSubtitle.getText(Long.MAX_VALUE));
+ }
+
+ public void testOverlappingSubtitleEventTimes() {
+ testSubtitleEventTimesHelper(overlappingSubtitle);
+ }
+
+ public void testOverlappingSubtitleEventIndices() {
+ testSubtitleEventIndicesHelper(overlappingSubtitle);
+ }
+
+ public void testOverlappingSubtitleText() {
+ // Test before first subtitle
+ assertNull(overlappingSubtitle.getText(0));
+ assertNull(overlappingSubtitle.getText(500000));
+ assertNull(overlappingSubtitle.getText(999999));
+
+ // Test first subtitle
+ assertEquals(FIRST_SUBTITLE_STRING, overlappingSubtitle.getText(1000000));
+ assertEquals(FIRST_SUBTITLE_STRING, overlappingSubtitle.getText(1500000));
+ assertEquals(FIRST_SUBTITLE_STRING, overlappingSubtitle.getText(1999999));
+
+ // Test after first and second subtitle
+ assertEquals(FIRST_AND_SECOND_SUBTITLE_STRING, overlappingSubtitle.getText(2000000));
+ assertEquals(FIRST_AND_SECOND_SUBTITLE_STRING, overlappingSubtitle.getText(2500000));
+ assertEquals(FIRST_AND_SECOND_SUBTITLE_STRING, overlappingSubtitle.getText(2999999));
+
+ // Test second subtitle
+ assertEquals(SECOND_SUBTITLE_STRING, overlappingSubtitle.getText(3000000));
+ assertEquals(SECOND_SUBTITLE_STRING, overlappingSubtitle.getText(3500000));
+ assertEquals(SECOND_SUBTITLE_STRING, overlappingSubtitle.getText(3999999));
+
+ // Test after second subtitle
+ assertNull(overlappingSubtitle.getText(4000000));
+ assertNull(overlappingSubtitle.getText(4500000));
+ assertNull(overlappingSubtitle.getText(Long.MAX_VALUE));
+ }
+
+ public void testNestedSubtitleEventTimes() {
+ testSubtitleEventTimesHelper(nestedSubtitle);
+ }
+
+ public void testNestedSubtitleEventIndices() {
+ testSubtitleEventIndicesHelper(nestedSubtitle);
+ }
+
+ public void testNestedSubtitleText() {
+ // Test before first subtitle
+ assertNull(nestedSubtitle.getText(0));
+ assertNull(nestedSubtitle.getText(500000));
+ assertNull(nestedSubtitle.getText(999999));
+
+ // Test first subtitle
+ assertEquals(FIRST_SUBTITLE_STRING, nestedSubtitle.getText(1000000));
+ assertEquals(FIRST_SUBTITLE_STRING, nestedSubtitle.getText(1500000));
+ assertEquals(FIRST_SUBTITLE_STRING, nestedSubtitle.getText(1999999));
+
+ // Test after first and second subtitle
+ assertEquals(FIRST_AND_SECOND_SUBTITLE_STRING, nestedSubtitle.getText(2000000));
+ assertEquals(FIRST_AND_SECOND_SUBTITLE_STRING, nestedSubtitle.getText(2500000));
+ assertEquals(FIRST_AND_SECOND_SUBTITLE_STRING, nestedSubtitle.getText(2999999));
+
+ // Test first subtitle
+ assertEquals(FIRST_SUBTITLE_STRING, nestedSubtitle.getText(3000000));
+ assertEquals(FIRST_SUBTITLE_STRING, nestedSubtitle.getText(3500000));
+ assertEquals(FIRST_SUBTITLE_STRING, nestedSubtitle.getText(3999999));
+
+ // Test after second subtitle
+ assertNull(nestedSubtitle.getText(4000000));
+ assertNull(nestedSubtitle.getText(4500000));
+ assertNull(nestedSubtitle.getText(Long.MAX_VALUE));
+ }
+
+ private void testSubtitleEventTimesHelper(WebvttSubtitle subtitle) {
+ assertEquals(1000000, subtitle.getEventTime(0));
+ assertEquals(2000000, subtitle.getEventTime(1));
+ assertEquals(3000000, subtitle.getEventTime(2));
+ assertEquals(4000000, subtitle.getEventTime(3));
+ }
+
+ private void testSubtitleEventIndicesHelper(WebvttSubtitle subtitle) {
+ // Test first event
+ assertEquals(0, subtitle.getNextEventTimeIndex(0));
+ assertEquals(0, subtitle.getNextEventTimeIndex(500000));
+ assertEquals(0, subtitle.getNextEventTimeIndex(999999));
+
+ // Test second event
+ assertEquals(1, subtitle.getNextEventTimeIndex(1000000));
+ assertEquals(1, subtitle.getNextEventTimeIndex(1500000));
+ assertEquals(1, subtitle.getNextEventTimeIndex(1999999));
+
+ // Test third event
+ assertEquals(2, subtitle.getNextEventTimeIndex(2000000));
+ assertEquals(2, subtitle.getNextEventTimeIndex(2500000));
+ assertEquals(2, subtitle.getNextEventTimeIndex(2999999));
+
+ // Test fourth event
+ assertEquals(3, subtitle.getNextEventTimeIndex(3000000));
+ assertEquals(3, subtitle.getNextEventTimeIndex(3500000));
+ assertEquals(3, subtitle.getNextEventTimeIndex(3999999));
+
+ // Test null event (i.e. look for events after the last event)
+ assertEquals(-1, subtitle.getNextEventTimeIndex(4000000));
+ assertEquals(-1, subtitle.getNextEventTimeIndex(4500000));
+ assertEquals(-1, subtitle.getNextEventTimeIndex(Long.MAX_VALUE));
+ }
+
+}
diff --git a/library/src/test/java/com/google/android/exoplayer/upstream/ByteArrayDataSourceTest.java b/library/src/test/java/com/google/android/exoplayer/upstream/ByteArrayDataSourceTest.java
new file mode 100644
index 0000000000..550cb149a8
--- /dev/null
+++ b/library/src/test/java/com/google/android/exoplayer/upstream/ByteArrayDataSourceTest.java
@@ -0,0 +1,151 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.google.android.exoplayer.upstream;
+
+import com.google.android.exoplayer.C;
+
+import junit.framework.TestCase;
+
+import java.io.IOException;
+
+/**
+ * Unit tests for {@link ByteArrayDataSource}.
+ */
+public class ByteArrayDataSourceTest extends TestCase {
+
+ private static final byte[] TEST_DATA = new byte[] {0, 1, 2, 3, 4, 5, 6, 7, 8, 9};
+ private static final byte[] TEST_DATA_ODD = new byte[] {0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10};
+
+ public void testFullReadSingleBytes() {
+ readTestData(TEST_DATA, 0, C.LENGTH_UNBOUNDED, 1, 0, 1, false);
+ }
+
+ public void testFullReadAllBytes() {
+ readTestData(TEST_DATA, 0, C.LENGTH_UNBOUNDED, 100, 0, 100, false);
+ }
+
+ public void testLimitReadSingleBytes() {
+ // Limit set to the length of the data.
+ readTestData(TEST_DATA, 0, TEST_DATA.length, 1, 0, 1, false);
+ // And less.
+ readTestData(TEST_DATA, 0, 6, 1, 0, 1, false);
+ }
+
+ public void testFullReadTwoBytes() {
+ // Try with the total data length an exact multiple of the size of each individual read.
+ readTestData(TEST_DATA, 0, C.LENGTH_UNBOUNDED, 2, 0, 2, false);
+ // And not.
+ readTestData(TEST_DATA_ODD, 0, C.LENGTH_UNBOUNDED, 2, 0, 2, false);
+ }
+
+ public void testLimitReadTwoBytes() {
+ // Try with the limit an exact multiple of the size of each individual read.
+ readTestData(TEST_DATA, 0, 6, 2, 0, 2, false);
+ // And not.
+ readTestData(TEST_DATA, 0, 7, 2, 0, 2, false);
+ }
+
+ public void testReadFromValidOffsets() {
+ // Read from an offset without bound.
+ readTestData(TEST_DATA, 1, C.LENGTH_UNBOUNDED, 1, 0, 1, false);
+ // And with bound.
+ readTestData(TEST_DATA, 1, 6, 1, 0, 1, false);
+ // Read from the last possible offset without bound.
+ readTestData(TEST_DATA, TEST_DATA.length - 1, C.LENGTH_UNBOUNDED, 1, 0, 1, false);
+ // And with bound.
+ readTestData(TEST_DATA, TEST_DATA.length - 1, 1, 1, 0, 1, false);
+ }
+
+ public void testReadFromInvalidOffsets() {
+ // Read from first invalid offset and check failure without bound.
+ readTestData(TEST_DATA, TEST_DATA.length, C.LENGTH_UNBOUNDED, 1, 0, 1, true);
+ // And with bound.
+ readTestData(TEST_DATA, TEST_DATA.length, 1, 1, 0, 1, true);
+ }
+
+ public void testReadWithInvalidLength() {
+ // Read more data than is available.
+ readTestData(TEST_DATA, 0, TEST_DATA.length + 1, 1, 0, 1, true);
+ // And with bound.
+ readTestData(TEST_DATA, 1, TEST_DATA.length, 1, 0, 1, true);
+ }
+
+ /**
+ * Tests reading from a {@link ByteArrayDataSource} with various parameters.
+ *
+ * @param testData The data that the {@link ByteArrayDataSource} will wrap.
+ * @param dataOffset The offset from which to read data.
+ * @param dataLength The total length of data to read.
+ * @param outputBufferLength The length of the target buffer for each read.
+ * @param writeOffset The offset into {@code outputBufferLength} for each read.
+ * @param maxReadLength The maximum length of each read.
+ * @param expectFailOnOpen Whether it is expected that opening the source will fail.
+ */
+ private void readTestData(byte[] testData, int dataOffset, int dataLength, int outputBufferLength,
+ int writeOffset, int maxReadLength, boolean expectFailOnOpen) {
+ int expectedFinalBytesRead =
+ dataLength == C.LENGTH_UNBOUNDED ? (testData.length - dataOffset) : dataLength;
+ ByteArrayDataSource dataSource = new ByteArrayDataSource(testData);
+ boolean opened = false;
+ try {
+ // Open the source.
+ long length = dataSource.open(new DataSpec(null, dataOffset, dataLength, null));
+ opened = true;
+ assertFalse(expectFailOnOpen);
+
+ // Verify the resolved length is as we expect.
+ assertEquals(expectedFinalBytesRead, length);
+
+ byte[] outputBuffer = new byte[outputBufferLength];
+ int accumulatedBytesRead = 0;
+ while (true) {
+ // Calculate a valid length for the next read, constraining by the specified output buffer
+ // length, write offset and maximum write length input parameters.
+ int requestedReadLength = Math.min(maxReadLength, outputBufferLength - writeOffset);
+ assertTrue(requestedReadLength > 0);
+
+ int bytesRead = dataSource.read(outputBuffer, writeOffset, requestedReadLength);
+ if (bytesRead != -1) {
+ assertTrue(bytesRead > 0);
+ assertTrue(bytesRead <= requestedReadLength);
+ // Check the data read was correct.
+ for (int i = 0; i < bytesRead; i++) {
+ assertEquals(testData[dataOffset + accumulatedBytesRead + i],
+ outputBuffer[writeOffset + i]);
+ }
+ // Check that we haven't read more data than we were expecting.
+ accumulatedBytesRead += bytesRead;
+ assertTrue(accumulatedBytesRead <= expectedFinalBytesRead);
+ // If we haven't read all of the bytes the request should have been satisfied in full.
+ assertTrue(accumulatedBytesRead == expectedFinalBytesRead
+ || bytesRead == requestedReadLength);
+ } else {
+ // We're done. Check we read the expected number of bytes.
+ assertEquals(expectedFinalBytesRead, accumulatedBytesRead);
+ return;
+ }
+ }
+ } catch (IOException e) {
+ if (expectFailOnOpen && !opened) {
+ // Expected.
+ return;
+ }
+ // Unexpected failure.
+ fail();
+ }
+ }
+
+}
diff --git a/library/src/test/java/com/google/android/exoplayer/upstream/DataSourceStreamTest.java b/library/src/test/java/com/google/android/exoplayer/upstream/DataSourceStreamTest.java
new file mode 100644
index 0000000000..7c40378116
--- /dev/null
+++ b/library/src/test/java/com/google/android/exoplayer/upstream/DataSourceStreamTest.java
@@ -0,0 +1,60 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.google.android.exoplayer.upstream;
+
+import com.google.android.exoplayer.testutil.Util;
+
+import junit.framework.TestCase;
+
+import java.io.IOException;
+import java.util.Arrays;
+
+/**
+ * Unit tests for {@link DataSourceStream}.
+ */
+public class DataSourceStreamTest extends TestCase {
+
+ private static final int DATA_LENGTH = 1024;
+ private static final int BUFFER_LENGTH = 128;
+
+ public void testGetLoadedData() throws IOException, InterruptedException {
+ byte[] testData = Util.buildTestData(DATA_LENGTH);
+ DataSource dataSource = new ByteArrayDataSource(testData);
+ DataSpec dataSpec = new DataSpec(null, 0, DATA_LENGTH, null);
+ DataSourceStream dataSourceStream = new DataSourceStream(dataSource, dataSpec,
+ new BufferPool(BUFFER_LENGTH));
+
+ dataSourceStream.load();
+ // Assert that the read and load positions are correct.
+ assertEquals(0, dataSourceStream.getReadPosition());
+ assertEquals(testData.length, dataSourceStream.getLoadPosition());
+
+ int halfTestDataLength = testData.length / 2;
+ byte[] readData = new byte[testData.length];
+ int bytesRead = dataSourceStream.read(readData, 0, halfTestDataLength);
+ // Assert that the read position is updated correctly.
+ assertEquals(halfTestDataLength, bytesRead);
+ assertEquals(halfTestDataLength, dataSourceStream.getReadPosition());
+
+ bytesRead += dataSourceStream.read(readData, bytesRead, testData.length - bytesRead);
+ // Assert that the read position was updated correctly.
+ assertEquals(testData.length, bytesRead);
+ assertEquals(testData.length, dataSourceStream.getReadPosition());
+ // Assert that the data read using the two read calls either side of getLoadedData is correct.
+ assertTrue(Arrays.equals(testData, readData));
+ }
+
+}
diff --git a/library/src/test/java/com/google/android/exoplayer/util/ParsableByteArrayTest.java b/library/src/test/java/com/google/android/exoplayer/util/ParsableByteArrayTest.java
new file mode 100644
index 0000000000..950710cf1d
--- /dev/null
+++ b/library/src/test/java/com/google/android/exoplayer/util/ParsableByteArrayTest.java
@@ -0,0 +1,124 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.google.android.exoplayer.util;
+
+import junit.framework.TestCase;
+
+import java.util.Arrays;
+
+/**
+ * Tests for {@link ParsableByteArray}.
+ */
+public class ParsableByteArrayTest extends TestCase {
+
+ private static final byte[] ARRAY_ELEMENTS =
+ new byte[] {0x0F, (byte) 0xFF, (byte) 0x42, (byte) 0x0F, 0x00, 0x00, 0x00, 0x00};
+
+ private ParsableByteArray parsableByteArray;
+
+ @Override
+ public void setUp() {
+ parsableByteArray = new ParsableByteArray(ARRAY_ELEMENTS.length);
+ System.arraycopy(ARRAY_ELEMENTS, 0, parsableByteArray.data, 0, ARRAY_ELEMENTS.length);
+ }
+
+ public void testReadInt() {
+ // When reading a signed integer
+ int value = parsableByteArray.readInt();
+
+ // Then the read value is equal to the array elements interpreted as an int.
+ assertEquals((0xFF & ARRAY_ELEMENTS[0]) << 24 | (0xFF & ARRAY_ELEMENTS[1]) << 16
+ | (0xFF & ARRAY_ELEMENTS[2]) << 8 | (0xFF & ARRAY_ELEMENTS[3]), value);
+ }
+
+ public void testSkipBack() {
+ // When reading an unsigned integer
+ long value = parsableByteArray.readUnsignedInt();
+
+ // Then skipping back and reading gives the same value.
+ parsableByteArray.skip(-4);
+ assertEquals(value, parsableByteArray.readUnsignedInt());
+ }
+
+ public void testReadingMovesPosition() {
+ // Given an array at the start
+ assertEquals(0, parsableByteArray.getPosition());
+
+ // When reading an integer, the position advances
+ parsableByteArray.readUnsignedInt();
+ assertEquals(4, parsableByteArray.getPosition());
+ }
+
+ public void testOutOfBoundsThrows() {
+ // Given an array at the end
+ parsableByteArray.readUnsignedLongToLong();
+ assertEquals(ARRAY_ELEMENTS.length, parsableByteArray.getPosition());
+
+ // Then reading more data throws.
+ try {
+ parsableByteArray.readUnsignedInt();
+ fail();
+ } catch (Exception e) {
+ // Expected.
+ }
+ }
+
+ public void testModificationsAffectParsableArray() {
+ // When modifying the wrapped byte array
+ byte[] data = parsableByteArray.data;
+ long readValue = parsableByteArray.readUnsignedInt();
+ data[0] = (byte) (ARRAY_ELEMENTS[0] + 1);
+ parsableByteArray.setPosition(0);
+
+ // Then the parsed value changes.
+ assertFalse(parsableByteArray.readUnsignedInt() == readValue);
+ }
+
+ public void testReadingUnsignedLongWithMsbSetThrows() {
+ // Given an array with the most-significant bit set on the top byte
+ byte[] data = parsableByteArray.data;
+ data[0] = (byte) 0x80;
+
+ // Then reading an unsigned long throws.
+ try {
+ parsableByteArray.readUnsignedLongToLong();
+ fail();
+ } catch (Exception e) {
+ // Expected.
+ }
+ }
+
+ public void testReadUnsignedFixedPoint1616() {
+ // When reading the integer part of a 16.16 fixed point value
+ int value = parsableByteArray.readUnsignedFixedPoint1616();
+
+ // Then the read value is equal to the array elements interpreted as a short.
+ assertEquals((0xFF & ARRAY_ELEMENTS[0]) << 8 | (ARRAY_ELEMENTS[1] & 0xFF), value);
+ assertEquals(4, parsableByteArray.getPosition());
+ }
+
+ public void testReadingBytesReturnsCopy() {
+ // When reading all the bytes back
+ int length = parsableByteArray.limit();
+ assertEquals(ARRAY_ELEMENTS.length, length);
+ byte[] copy = new byte[length];
+ parsableByteArray.readBytes(copy, 0, length);
+
+ // Then the array elements are the same.
+ assertTrue(Arrays.equals(parsableByteArray.data, copy));
+ }
+
+}
diff --git a/library/src/test/java/com/google/android/exoplayer/util/UriUtilTest.java b/library/src/test/java/com/google/android/exoplayer/util/UriUtilTest.java
new file mode 100644
index 0000000000..f482d99c47
--- /dev/null
+++ b/library/src/test/java/com/google/android/exoplayer/util/UriUtilTest.java
@@ -0,0 +1,98 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.google.android.exoplayer.util;
+
+import junit.framework.TestCase;
+
+/**
+ * Unit tests for {@link UriUtil}.
+ */
+public class UriUtilTest extends TestCase {
+
+ /**
+ * Tests normal usage of {@link UriUtil#resolve(String, String)}.
+ *
+ * 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)}.
+ *
+ * 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"));
+ }
+
+}
diff --git a/library/src/test/java/com/google/android/exoplayer/util/UtilTest.java b/library/src/test/java/com/google/android/exoplayer/util/UtilTest.java
new file mode 100644
index 0000000000..351e93c339
--- /dev/null
+++ b/library/src/test/java/com/google/android/exoplayer/util/UtilTest.java
@@ -0,0 +1,147 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.google.android.exoplayer.util;
+
+import junit.framework.TestCase;
+
+import java.text.ParseException;
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * Unit tests for {@link Util}.
+ */
+public class UtilTest extends TestCase {
+
+ public void testArrayBinarySearchFloor() {
+ long[] values = new long[0];
+ assertEquals(-1, Util.binarySearchFloor(values, 0, false, false));
+ assertEquals(0, Util.binarySearchFloor(values, 0, false, true));
+
+ values = new long[] {1, 3, 5};
+ assertEquals(-1, Util.binarySearchFloor(values, 0, false, false));
+ assertEquals(-1, Util.binarySearchFloor(values, 0, true, false));
+ assertEquals(0, Util.binarySearchFloor(values, 0, false, true));
+ assertEquals(0, Util.binarySearchFloor(values, 0, true, true));
+
+ assertEquals(-1, Util.binarySearchFloor(values, 1, false, false));
+ assertEquals(0, Util.binarySearchFloor(values, 1, true, false));
+ assertEquals(0, Util.binarySearchFloor(values, 1, false, true));
+ assertEquals(0, Util.binarySearchFloor(values, 1, true, true));
+
+ assertEquals(1, Util.binarySearchFloor(values, 4, false, false));
+ assertEquals(1, Util.binarySearchFloor(values, 4, true, false));
+
+ assertEquals(1, Util.binarySearchFloor(values, 5, false, false));
+ assertEquals(2, Util.binarySearchFloor(values, 5, true, false));
+
+ assertEquals(2, Util.binarySearchFloor(values, 6, false, false));
+ assertEquals(2, Util.binarySearchFloor(values, 6, true, false));
+ }
+
+ public void testListBinarySearchFloor() {
+ List values = new ArrayList();
+ 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 values = new ArrayList();
+ 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"));
+ }
+
+}
diff --git a/library/src/test/libs/.README.txt b/library/src/test/libs/.README.txt
new file mode 100644
index 0000000000..3f37353a9d
--- /dev/null
+++ b/library/src/test/libs/.README.txt
@@ -0,0 +1 @@
+This file is needed to make sure the libs directory is present.
diff --git a/library/src/test/project.properties b/library/src/test/project.properties
new file mode 100644
index 0000000000..6e18427a42
--- /dev/null
+++ b/library/src/test/project.properties
@@ -0,0 +1,14 @@
+# This file is automatically generated by Android Tools.
+# Do not modify this file -- YOUR CHANGES WILL BE ERASED!
+#
+# This file must be checked in Version Control Systems.
+#
+# To customize properties used by the Ant build system edit
+# "ant.properties", and override values to adapt the script to your
+# project structure.
+#
+# To enable ProGuard to shrink and obfuscate your code, uncomment this (available properties: sdk.dir, user.home):
+#proguard.config=${sdk.dir}/tools/proguard/proguard-android.txt:proguard-project.txt
+
+# Project target.
+target=android-21
diff --git a/library/src/test/res/.README.txt b/library/src/test/res/.README.txt
new file mode 100644
index 0000000000..c27147ce56
--- /dev/null
+++ b/library/src/test/res/.README.txt
@@ -0,0 +1,2 @@
+This file is needed to make sure the res directory is present.
+The file is ignored by the Android toolchain because its name starts with a dot.
diff --git a/third_party/dexmaker/LICENSE b/third_party/dexmaker/LICENSE
new file mode 100644
index 0000000000..261eeb9e9f
--- /dev/null
+++ b/third_party/dexmaker/LICENSE
@@ -0,0 +1,201 @@
+ Apache License
+ Version 2.0, January 2004
+ http://www.apache.org/licenses/
+
+ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
+
+ 1. Definitions.
+
+ "License" shall mean the terms and conditions for use, reproduction,
+ and distribution as defined by Sections 1 through 9 of this document.
+
+ "Licensor" shall mean the copyright owner or entity authorized by
+ the copyright owner that is granting the License.
+
+ "Legal Entity" shall mean the union of the acting entity and all
+ other entities that control, are controlled by, or are under common
+ control with that entity. For the purposes of this definition,
+ "control" means (i) the power, direct or indirect, to cause the
+ direction or management of such entity, whether by contract or
+ otherwise, or (ii) ownership of fifty percent (50%) or more of the
+ outstanding shares, or (iii) beneficial ownership of such entity.
+
+ "You" (or "Your") shall mean an individual or Legal Entity
+ exercising permissions granted by this License.
+
+ "Source" form shall mean the preferred form for making modifications,
+ including but not limited to software source code, documentation
+ source, and configuration files.
+
+ "Object" form shall mean any form resulting from mechanical
+ transformation or translation of a Source form, including but
+ not limited to compiled object code, generated documentation,
+ and conversions to other media types.
+
+ "Work" shall mean the work of authorship, whether in Source or
+ Object form, made available under the License, as indicated by a
+ copyright notice that is included in or attached to the work
+ (an example is provided in the Appendix below).
+
+ "Derivative Works" shall mean any work, whether in Source or Object
+ form, that is based on (or derived from) the Work and for which the
+ editorial revisions, annotations, elaborations, or other modifications
+ represent, as a whole, an original work of authorship. For the purposes
+ of this License, Derivative Works shall not include works that remain
+ separable from, or merely link (or bind by name) to the interfaces of,
+ the Work and Derivative Works thereof.
+
+ "Contribution" shall mean any work of authorship, including
+ the original version of the Work and any modifications or additions
+ to that Work or Derivative Works thereof, that is intentionally
+ submitted to Licensor for inclusion in the Work by the copyright owner
+ or by an individual or Legal Entity authorized to submit on behalf of
+ the copyright owner. For the purposes of this definition, "submitted"
+ means any form of electronic, verbal, or written communication sent
+ to the Licensor or its representatives, including but not limited to
+ communication on electronic mailing lists, source code control systems,
+ and issue tracking systems that are managed by, or on behalf of, the
+ Licensor for the purpose of discussing and improving the Work, but
+ excluding communication that is conspicuously marked or otherwise
+ designated in writing by the copyright owner as "Not a Contribution."
+
+ "Contributor" shall mean Licensor and any individual or Legal Entity
+ on behalf of whom a Contribution has been received by Licensor and
+ subsequently incorporated within the Work.
+
+ 2. Grant of Copyright License. Subject to the terms and conditions of
+ this License, each Contributor hereby grants to You a perpetual,
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+ copyright license to reproduce, prepare Derivative Works of,
+ publicly display, publicly perform, sublicense, and distribute the
+ Work and such Derivative Works in Source or Object form.
+
+ 3. Grant of Patent License. Subject to the terms and conditions of
+ this License, each Contributor hereby grants to You a perpetual,
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+ (except as stated in this section) patent license to make, have made,
+ use, offer to sell, sell, import, and otherwise transfer the Work,
+ where such license applies only to those patent claims licensable
+ by such Contributor that are necessarily infringed by their
+ Contribution(s) alone or by combination of their Contribution(s)
+ with the Work to which such Contribution(s) was submitted. If You
+ institute patent litigation against any entity (including a
+ cross-claim or counterclaim in a lawsuit) alleging that the Work
+ or a Contribution incorporated within the Work constitutes direct
+ or contributory patent infringement, then any patent licenses
+ granted to You under this License for that Work shall terminate
+ as of the date such litigation is filed.
+
+ 4. Redistribution. You may reproduce and distribute copies of the
+ Work or Derivative Works thereof in any medium, with or without
+ modifications, and in Source or Object form, provided that You
+ meet the following conditions:
+
+ (a) You must give any other recipients of the Work or
+ Derivative Works a copy of this License; and
+
+ (b) You must cause any modified files to carry prominent notices
+ stating that You changed the files; and
+
+ (c) You must retain, in the Source form of any Derivative Works
+ that You distribute, all copyright, patent, trademark, and
+ attribution notices from the Source form of the Work,
+ excluding those notices that do not pertain to any part of
+ the Derivative Works; and
+
+ (d) If the Work includes a "NOTICE" text file as part of its
+ distribution, then any Derivative Works that You distribute must
+ include a readable copy of the attribution notices contained
+ within such NOTICE file, excluding those notices that do not
+ pertain to any part of the Derivative Works, in at least one
+ of the following places: within a NOTICE text file distributed
+ as part of the Derivative Works; within the Source form or
+ documentation, if provided along with the Derivative Works; or,
+ within a display generated by the Derivative Works, if and
+ wherever such third-party notices normally appear. The contents
+ of the NOTICE file are for informational purposes only and
+ do not modify the License. You may add Your own attribution
+ notices within Derivative Works that You distribute, alongside
+ or as an addendum to the NOTICE text from the Work, provided
+ that such additional attribution notices cannot be construed
+ as modifying the License.
+
+ You may add Your own copyright statement to Your modifications and
+ may provide additional or different license terms and conditions
+ for use, reproduction, or distribution of Your modifications, or
+ for any such Derivative Works as a whole, provided Your use,
+ reproduction, and distribution of the Work otherwise complies with
+ the conditions stated in this License.
+
+ 5. Submission of Contributions. Unless You explicitly state otherwise,
+ any Contribution intentionally submitted for inclusion in the Work
+ by You to the Licensor shall be under the terms and conditions of
+ this License, without any additional terms or conditions.
+ Notwithstanding the above, nothing herein shall supersede or modify
+ the terms of any separate license agreement you may have executed
+ with Licensor regarding such Contributions.
+
+ 6. Trademarks. This License does not grant permission to use the trade
+ names, trademarks, service marks, or product names of the Licensor,
+ except as required for reasonable and customary use in describing the
+ origin of the Work and reproducing the content of the NOTICE file.
+
+ 7. Disclaimer of Warranty. Unless required by applicable law or
+ agreed to in writing, Licensor provides the Work (and each
+ Contributor provides its Contributions) on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
+ implied, including, without limitation, any warranties or conditions
+ of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
+ PARTICULAR PURPOSE. You are solely responsible for determining the
+ appropriateness of using or redistributing the Work and assume any
+ risks associated with Your exercise of permissions under this License.
+
+ 8. Limitation of Liability. In no event and under no legal theory,
+ whether in tort (including negligence), contract, or otherwise,
+ unless required by applicable law (such as deliberate and grossly
+ negligent acts) or agreed to in writing, shall any Contributor be
+ liable to You for damages, including any direct, indirect, special,
+ incidental, or consequential damages of any character arising as a
+ result of this License or out of the use or inability to use the
+ Work (including but not limited to damages for loss of goodwill,
+ work stoppage, computer failure or malfunction, or any and all
+ other commercial damages or losses), even if such Contributor
+ has been advised of the possibility of such damages.
+
+ 9. Accepting Warranty or Additional Liability. While redistributing
+ the Work or Derivative Works thereof, You may choose to offer,
+ and charge a fee for, acceptance of support, warranty, indemnity,
+ or other liability obligations and/or rights consistent with this
+ License. However, in accepting such obligations, You may act only
+ on Your own behalf and on Your sole responsibility, not on behalf
+ of any other Contributor, and only if You agree to indemnify,
+ defend, and hold each Contributor harmless for any liability
+ incurred by, or claims asserted against, such Contributor by reason
+ of your accepting any such warranty or additional liability.
+
+ END OF TERMS AND CONDITIONS
+
+ APPENDIX: How to apply the Apache License to your work.
+
+ To apply the Apache License to your work, attach the following
+ boilerplate notice, with the fields enclosed by brackets "[]"
+ replaced with your own identifying information. (Don't include
+ the brackets!) The text should be enclosed in the appropriate
+ comment syntax for the file format. We also recommend that a
+ file or class name and description of purpose be included on the
+ same "printed page" as the copyright notice for easier
+ identification within third-party archives.
+
+ Copyright [yyyy] [name of copyright owner]
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
diff --git a/third_party/dexmaker/dexmaker-1.2.jar b/third_party/dexmaker/dexmaker-1.2.jar
new file mode 100644
index 0000000000..08d1b85f18
Binary files /dev/null and b/third_party/dexmaker/dexmaker-1.2.jar differ
diff --git a/third_party/dexmaker/dexmaker-mockito-1.2.jar b/third_party/dexmaker/dexmaker-mockito-1.2.jar
new file mode 100644
index 0000000000..a3e19759c2
Binary files /dev/null and b/third_party/dexmaker/dexmaker-mockito-1.2.jar differ
diff --git a/third_party/mockito/LICENSE b/third_party/mockito/LICENSE
new file mode 100644
index 0000000000..2a5730a7ce
--- /dev/null
+++ b/third_party/mockito/LICENSE
@@ -0,0 +1,21 @@
+The MIT License
+
+Copyright (c) 2008-2010 Mockito contributors
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in
+all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+THE SOFTWARE.
diff --git a/third_party/mockito/mockito-all-1.9.5.jar b/third_party/mockito/mockito-all-1.9.5.jar
new file mode 100644
index 0000000000..00416eb387
Binary files /dev/null and b/third_party/mockito/mockito-all-1.9.5.jar differ