diff --git a/library/src/test/.classpath b/library/src/test/.classpath new file mode 100644 index 0000000000..171a8c3ec8 --- /dev/null +++ b/library/src/test/.classpath @@ -0,0 +1,10 @@ + + + + + + + + + + diff --git a/library/src/test/.project b/library/src/test/.project new file mode 100644 index 0000000000..d63886b065 --- /dev/null +++ b/library/src/test/.project @@ -0,0 +1,62 @@ + + + ExoPlayerTests + + + ExoPlayerLib + + + + com.android.ide.eclipse.adt.ResourceManagerBuilder + + + + + com.android.ide.eclipse.adt.PreCompilerBuilder + + + + + org.eclipse.jdt.core.javabuilder + + + + + com.android.ide.eclipse.adt.ApkBuilder + + + + + + com.android.ide.eclipse.adt.AndroidNature + org.eclipse.jdt.core.javanature + + + + libs/dexmaker-1.2.jar + 1 + $%7BPARENT-3-PROJECT_LOC%7D/third_party/dexmaker/dexmaker-1.2.jar + + + libs/dexmaker-mockito-1.2.jar + 1 + $%7BPARENT-3-PROJECT_LOC%7D/third_party/dexmaker/dexmaker-mockito-1.2.jar + + + libs/mockito-all-1.9.5.jar + 1 + $%7BPARENT-3-PROJECT_LOC%7D/third_party/mockito/mockito-all-1.9.5.jar + + + + + 1425657306619 + + 14 + + org.eclipse.ui.ide.multiFilter + 1.0-name-matches-true-false-BUILD + + + + diff --git a/library/src/test/AndroidManifest.xml b/library/src/test/AndroidManifest.xml new file mode 100644 index 0000000000..517161f3b9 --- /dev/null +++ b/library/src/test/AndroidManifest.xml @@ -0,0 +1,30 @@ + + + + + + + + + + + + + + diff --git a/library/src/test/assets/dash/sample_mpd_1 b/library/src/test/assets/dash/sample_mpd_1 new file mode 100755 index 0000000000..07bcdd4f50 --- /dev/null +++ b/library/src/test/assets/dash/sample_mpd_1 @@ -0,0 +1,99 @@ + + + + + + + + + + + + + + + +http://www.test.com/141 + + + + + + + + + + + + + +http://www.test.com/135 + + + + + + + + + + + + + +http://www.test.com/vtt + + + + + + + + + + + diff --git a/library/src/test/assets/webm/vorbis_codec_private b/library/src/test/assets/webm/vorbis_codec_private new file mode 100644 index 0000000000..6a613449a7 Binary files /dev/null and b/library/src/test/assets/webm/vorbis_codec_private differ diff --git a/library/src/test/assets/webvtt/empty b/library/src/test/assets/webvtt/empty new file mode 100644 index 0000000000..8b13789179 --- /dev/null +++ b/library/src/test/assets/webvtt/empty @@ -0,0 +1 @@ + diff --git a/library/src/test/assets/webvtt/typical b/library/src/test/assets/webvtt/typical new file mode 100644 index 0000000000..d1395efe1b --- /dev/null +++ b/library/src/test/assets/webvtt/typical @@ -0,0 +1,8 @@ +WEBVTT +X-TIMESTAMP-MAP=LOCAL:00:00.000,MPEGTS:450000 + +00:00.000 --> 00:01.234 +This is the first subtitle. + +00:02.345 --> 00:03.456 +This is the second subtitle. diff --git a/library/src/test/assets/webvtt/typical_with_identifiers b/library/src/test/assets/webvtt/typical_with_identifiers new file mode 100644 index 0000000000..e2c5df065b --- /dev/null +++ b/library/src/test/assets/webvtt/typical_with_identifiers @@ -0,0 +1,10 @@ +WEBVTT +X-TIMESTAMP-MAP=LOCAL:00:00.000,MPEGTS:450000 + +1 +00:00.000 --> 00:01.234 +This is the first subtitle. + +2 +00:02.345 --> 00:03.456 +This is the second subtitle. diff --git a/library/src/test/assets/webvtt/typical_with_tags b/library/src/test/assets/webvtt/typical_with_tags new file mode 100644 index 0000000000..36e630e240 --- /dev/null +++ b/library/src/test/assets/webvtt/typical_with_tags @@ -0,0 +1,14 @@ +WEBVTT +X-TIMESTAMP-MAP=LOCAL:00:00.000,MPEGTS:450000 + +00:00.000 --> 00:01.234 +This is the first subtitle. + +00:02.345 --> 00:03.456 +This is the second subtitle. + +00:04.000 --> 00:05.000 +This is the third subtitle. + +00:06.000 --> 00:07.000 +This is the <fourth> &subtitle. diff --git a/library/src/test/java/com/google/android/exoplayer/MediaFormatTest.java b/library/src/test/java/com/google/android/exoplayer/MediaFormatTest.java new file mode 100644 index 0000000000..c9d50c43b0 --- /dev/null +++ b/library/src/test/java/com/google/android/exoplayer/MediaFormatTest.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; + +import com.google.android.exoplayer.util.Util; + +import android.annotation.TargetApi; + +import junit.framework.TestCase; + +import java.util.ArrayList; +import java.util.List; + +/** + * Unit test for {@link MediaFormat}. + */ +public class MediaFormatTest extends TestCase { + + public void testConversionToFrameworkFormat() { + if (Util.SDK_INT < 16) { + // Test doesn't apply. + return; + } + + byte[] initData1 = new byte[] {1, 2, 3}; + byte[] initData2 = new byte[] {4, 5, 6}; + List initData = new ArrayList(); + 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