diff --git a/README.md b/README.md index 7f35329516..8755ac588d 100644 --- a/README.md +++ b/README.md @@ -42,7 +42,7 @@ Next add a gradle compile dependency to the `build.gradle` file of your app module. The following will add a dependency to the full library: ```gradle -compile 'com.google.android.exoplayer:exoplayer:2.X.X' +implementation 'com.google.android.exoplayer:exoplayer:2.X.X' ``` where `2.X.X` is your preferred version. Alternatively, you can depend on only @@ -51,9 +51,9 @@ dependencies on the Core, DASH and UI library modules, as might be required for an app that plays DASH content: ```gradle -compile 'com.google.android.exoplayer:exoplayer-core:2.X.X' -compile 'com.google.android.exoplayer:exoplayer-dash:2.X.X' -compile 'com.google.android.exoplayer:exoplayer-ui:2.X.X' +implementation 'com.google.android.exoplayer:exoplayer-core:2.X.X' +implementation 'com.google.android.exoplayer:exoplayer-dash:2.X.X' +implementation 'com.google.android.exoplayer:exoplayer-ui:2.X.X' ``` The available library modules are listed below. Adding a dependency to the full @@ -105,9 +105,9 @@ You should now see the ExoPlayer modules appear as part of your project. You can depend on them as you would on any other local module, for example: ```gradle -compile project(':exoplayer-library-core') -compile project(':exoplayer-library-dash') -compile project(':exoplayer-library-ui') +implementation project(':exoplayer-library-core') +implementation project(':exoplayer-library-dash') +implementation project(':exoplayer-library-ui') ``` ## Developing ExoPlayer ## diff --git a/RELEASENOTES.md b/RELEASENOTES.md index f9a03390ef..17e5b19bca 100644 --- a/RELEASENOTES.md +++ b/RELEASENOTES.md @@ -1,5 +1,25 @@ # Release notes # +### 2.7.1 ### + +* Gradle: Replaced 'compile' (deprecated) with 'implementation' and + 'api'. This may lead to build breakage for applications upgrading from + previous version that rely on indirect dependencies of certain modules. In + such cases, application developers need to add the missing dependency to + their gradle file. You can read more about the new dependency configurations + [here](https://developer.android.com/studio/build/gradle-plugin-3-0-0-migration.html#new_configurations). +* HlsMediaSource: Make HLS periods start at zero instead of the epoch. + Applications that rely on HLS timelines having a period starting at + the epoch will need to update their handling of HLS timelines. The program + date time is still available via the informational + `Timeline.Window.windowStartTimeMs` field + ([#3865](https://github.com/google/ExoPlayer/issues/3865), + [#3888](https://github.com/google/ExoPlayer/issues/3888)). +* Enable seeking in MP4 streams where duration is set incorrectly in the track + header ([#3926](https://github.com/google/ExoPlayer/issues/3926)). +* Video: Force rendering a frame periodically in `MediaCodecVideoRenderer` and + `LibvpxVideoRenderer`, even if it is late. + ### 2.7.0 ### * Player interface: @@ -21,7 +41,7 @@ * Add `ExoPlayer.setSeekParameters` for controlling how seek operations are performed. The `SeekParameters` class contains defaults for exact seeking and seeking to the closest sync points before, either side or after specified seek - positions. `SeekParameters` are not currently supported when playing HLS + positions. `SeekParameters` are not currently supported when playing HLS streams. * DefaultTrackSelector: * Replace `DefaultTrackSelector.Parameters` copy methods with a builder. diff --git a/constants.gradle b/constants.gradle index b02e2d4c37..3e75567231 100644 --- a/constants.gradle +++ b/constants.gradle @@ -12,13 +12,16 @@ // See the License for the specific language governing permissions and // limitations under the License. project.ext { + // ExoPlayer version and version code. + releaseVersion = '2.7.1' + releaseVersionCode = 2701 // Important: ExoPlayer specifies a minSdkVersion of 14 because various // components provided by the library may be of use on older devices. // However, please note that the core media playback functionality provided // by the library requires API level 16 or greater. minSdkVersion = 14 - compileSdkVersion = 27 targetSdkVersion = 27 + compileSdkVersion = 27 buildToolsVersion = '26.0.2' testSupportLibraryVersion = '0.5' supportLibraryVersion = '27.0.0' @@ -28,7 +31,6 @@ project.ext { junitVersion = '4.12' truthVersion = '0.39' robolectricVersion = '3.7.1' - releaseVersion = '2.7.0' modulePrefix = ':' if (gradle.ext.has('exoplayerModulePrefix')) { modulePrefix += gradle.ext.exoplayerModulePrefix diff --git a/core_settings.gradle b/core_settings.gradle index 20a7c87bde..c4914e3040 100644 --- a/core_settings.gradle +++ b/core_settings.gradle @@ -24,6 +24,7 @@ include modulePrefix + 'library-hls' include modulePrefix + 'library-smoothstreaming' include modulePrefix + 'library-ui' include modulePrefix + 'testutils' +include modulePrefix + 'testutils-robolectric' include modulePrefix + 'extension-ffmpeg' include modulePrefix + 'extension-flac' include modulePrefix + 'extension-gvr' @@ -43,6 +44,7 @@ project(modulePrefix + 'library-hls').projectDir = new File(rootDir, 'library/hl project(modulePrefix + 'library-smoothstreaming').projectDir = new File(rootDir, 'library/smoothstreaming') project(modulePrefix + 'library-ui').projectDir = new File(rootDir, 'library/ui') project(modulePrefix + 'testutils').projectDir = new File(rootDir, 'testutils') +project(modulePrefix + 'testutils-robolectric').projectDir = new File(rootDir, 'testutils_robolectric') project(modulePrefix + 'extension-ffmpeg').projectDir = new File(rootDir, 'extensions/ffmpeg') project(modulePrefix + 'extension-flac').projectDir = new File(rootDir, 'extensions/flac') project(modulePrefix + 'extension-gvr').projectDir = new File(rootDir, 'extensions/gvr') diff --git a/demos/cast/build.gradle b/demos/cast/build.gradle index 8f074c9238..c928d0e46e 100644 --- a/demos/cast/build.gradle +++ b/demos/cast/build.gradle @@ -19,6 +19,8 @@ android { buildToolsVersion project.ext.buildToolsVersion defaultConfig { + versionName project.ext.releaseVersion + versionCode project.ext.releaseVersionCode minSdkVersion 16 targetSdkVersion project.ext.targetSdkVersion } @@ -42,11 +44,13 @@ android { } dependencies { - compile project(modulePrefix + 'library-core') - compile project(modulePrefix + 'library-dash') - compile project(modulePrefix + 'library-hls') - compile project(modulePrefix + 'library-smoothstreaming') - compile project(modulePrefix + 'library-ui') - compile project(modulePrefix + 'extension-cast') - compile 'com.android.support:recyclerview-v7:' + supportLibraryVersion + implementation project(modulePrefix + 'library-core') + implementation project(modulePrefix + 'library-dash') + implementation project(modulePrefix + 'library-hls') + implementation project(modulePrefix + 'library-smoothstreaming') + implementation project(modulePrefix + 'library-ui') + implementation project(modulePrefix + 'extension-cast') + implementation 'com.android.support:support-v4:' + supportLibraryVersion + implementation 'com.android.support:appcompat-v7:' + supportLibraryVersion + implementation 'com.android.support:recyclerview-v7:' + supportLibraryVersion } diff --git a/demos/cast/src/main/AndroidManifest.xml b/demos/cast/src/main/AndroidManifest.xml index d23576572a..ae16776333 100644 --- a/demos/cast/src/main/AndroidManifest.xml +++ b/demos/cast/src/main/AndroidManifest.xml @@ -14,12 +14,10 @@ limitations under the License. --> + package="com.google.android.exoplayer2.castdemo"> - + diff --git a/demos/cast/src/main/java/com/google/android/exoplayer2/castdemo/PlayerManager.java b/demos/cast/src/main/java/com/google/android/exoplayer2/castdemo/PlayerManager.java index ac488ff3fd..a14978a46a 100644 --- a/demos/cast/src/main/java/com/google/android/exoplayer2/castdemo/PlayerManager.java +++ b/demos/cast/src/main/java/com/google/android/exoplayer2/castdemo/PlayerManager.java @@ -25,6 +25,7 @@ import com.google.android.exoplayer2.ExoPlayerFactory; import com.google.android.exoplayer2.Player; import com.google.android.exoplayer2.Player.DefaultEventListener; import com.google.android.exoplayer2.Player.DiscontinuityReason; +import com.google.android.exoplayer2.Player.TimelineChangeReason; import com.google.android.exoplayer2.RenderersFactory; import com.google.android.exoplayer2.SimpleExoPlayer; import com.google.android.exoplayer2.Timeline; @@ -281,8 +282,12 @@ import java.util.ArrayList; } @Override - public void onTimelineChanged(Timeline timeline, Object manifest) { + public void onTimelineChanged( + Timeline timeline, Object manifest, @TimelineChangeReason int reason) { updateCurrentItemIndex(); + if (timeline.isEmpty()) { + castMediaQueueCreationPending = true; + } } // CastPlayer.SessionAvailabilityListener implementation. diff --git a/demos/ima/build.gradle b/demos/ima/build.gradle index 5225c260f8..35c2daf88e 100644 --- a/demos/ima/build.gradle +++ b/demos/ima/build.gradle @@ -19,6 +19,8 @@ android { buildToolsVersion project.ext.buildToolsVersion defaultConfig { + versionName project.ext.releaseVersion + versionCode project.ext.releaseVersionCode minSdkVersion 16 targetSdkVersion project.ext.targetSdkVersion } @@ -41,10 +43,11 @@ android { } dependencies { - compile project(modulePrefix + 'library-core') - compile project(modulePrefix + 'library-ui') - compile project(modulePrefix + 'library-dash') - compile project(modulePrefix + 'library-hls') - compile project(modulePrefix + 'library-smoothstreaming') - compile project(modulePrefix + 'extension-ima') + implementation project(modulePrefix + 'library-core') + implementation project(modulePrefix + 'library-ui') + implementation project(modulePrefix + 'library-dash') + implementation project(modulePrefix + 'library-hls') + implementation project(modulePrefix + 'library-smoothstreaming') + implementation project(modulePrefix + 'extension-ima') + implementation 'com.android.support:support-annotations:' + supportLibraryVersion } diff --git a/demos/ima/src/main/AndroidManifest.xml b/demos/ima/src/main/AndroidManifest.xml index 7f169b8095..50ad0c1b54 100644 --- a/demos/ima/src/main/AndroidManifest.xml +++ b/demos/ima/src/main/AndroidManifest.xml @@ -14,12 +14,10 @@ limitations under the License. --> + package="com.google.android.exoplayer2.imademo"> - + diff --git a/demos/main/build.gradle b/demos/main/build.gradle index f637e39ce4..ce0992eb7a 100644 --- a/demos/main/build.gradle +++ b/demos/main/build.gradle @@ -19,6 +19,8 @@ android { buildToolsVersion project.ext.buildToolsVersion defaultConfig { + versionName project.ext.releaseVersion + versionCode project.ext.releaseVersionCode minSdkVersion 16 targetSdkVersion project.ext.targetSdkVersion } @@ -55,15 +57,16 @@ android { } dependencies { - compile project(modulePrefix + 'library-core') - compile project(modulePrefix + 'library-dash') - compile project(modulePrefix + 'library-hls') - compile project(modulePrefix + 'library-smoothstreaming') - compile project(modulePrefix + 'library-ui') - withExtensionsCompile project(path: modulePrefix + 'extension-ffmpeg') - withExtensionsCompile project(path: modulePrefix + 'extension-flac') - withExtensionsCompile project(path: modulePrefix + 'extension-ima') - withExtensionsCompile project(path: modulePrefix + 'extension-opus') - withExtensionsCompile project(path: modulePrefix + 'extension-vp9') - withExtensionsCompile project(path: modulePrefix + 'extension-rtmp') + implementation 'com.android.support:support-annotations:' + supportLibraryVersion + implementation project(modulePrefix + 'library-core') + implementation project(modulePrefix + 'library-dash') + implementation project(modulePrefix + 'library-hls') + implementation project(modulePrefix + 'library-smoothstreaming') + implementation project(modulePrefix + 'library-ui') + withExtensionsImplementation project(path: modulePrefix + 'extension-ffmpeg') + withExtensionsImplementation project(path: modulePrefix + 'extension-flac') + withExtensionsImplementation project(path: modulePrefix + 'extension-ima') + withExtensionsImplementation project(path: modulePrefix + 'extension-opus') + withExtensionsImplementation project(path: modulePrefix + 'extension-vp9') + withExtensionsImplementation project(path: modulePrefix + 'extension-rtmp') } diff --git a/demos/main/src/main/AndroidManifest.xml b/demos/main/src/main/AndroidManifest.xml index a98176d93b..cde95300ab 100644 --- a/demos/main/src/main/AndroidManifest.xml +++ b/demos/main/src/main/AndroidManifest.xml @@ -15,15 +15,13 @@ --> + package="com.google.android.exoplayer2.demo"> - + + + + + + + + diff --git a/extensions/cast/src/test/java/com/google/android/exoplayer2/ext/cast/CastTimelineTrackerTest.java b/extensions/cast/src/test/java/com/google/android/exoplayer2/ext/cast/CastTimelineTrackerTest.java index bf4b20e156..4c60e7c0b3 100644 --- a/extensions/cast/src/test/java/com/google/android/exoplayer2/ext/cast/CastTimelineTrackerTest.java +++ b/extensions/cast/src/test/java/com/google/android/exoplayer2/ext/cast/CastTimelineTrackerTest.java @@ -17,6 +17,7 @@ package com.google.android.exoplayer2.ext.cast; import com.google.android.exoplayer2.C; import com.google.android.exoplayer2.testutil.TimelineAsserts; +import com.google.android.exoplayer2.util.MimeTypes; import com.google.android.gms.cast.MediaInfo; import com.google.android.gms.cast.MediaQueueItem; import com.google.android.gms.cast.MediaStatus; @@ -25,11 +26,9 @@ import org.junit.Test; import org.junit.runner.RunWith; import org.mockito.Mockito; import org.robolectric.RobolectricTestRunner; -import org.robolectric.annotation.Config; /** Tests for {@link CastTimelineTracker}. */ @RunWith(RobolectricTestRunner.class) -@Config(sdk = Config.TARGET_SDK, manifest = Config.NONE) public class CastTimelineTrackerTest { private static final long DURATION_1_MS = 1000; @@ -49,12 +48,12 @@ public class CastTimelineTrackerTest { new long[] {DURATION_1_MS, MediaInfo.UNKNOWN_DURATION, MediaInfo.UNKNOWN_DURATION}); CastTimelineTracker tracker = new CastTimelineTracker(); - mediaInfo = mockMediaInfo("contentId1", DURATION_1_MS); + mediaInfo = getMediaInfo("contentId1", DURATION_1_MS); Mockito.when(status.getMediaInfo()).thenReturn(mediaInfo); TimelineAsserts.assertPeriodDurations( tracker.getCastTimeline(status), C.msToUs(DURATION_1_MS), C.TIME_UNSET, C.TIME_UNSET); - mediaInfo = mockMediaInfo("contentId3", DURATION_3_MS); + mediaInfo = getMediaInfo("contentId3", DURATION_3_MS); Mockito.when(status.getMediaInfo()).thenReturn(mediaInfo); TimelineAsserts.assertPeriodDurations( tracker.getCastTimeline(status), @@ -62,7 +61,7 @@ public class CastTimelineTrackerTest { C.TIME_UNSET, C.msToUs(DURATION_3_MS)); - mediaInfo = mockMediaInfo("contentId2", DURATION_2_MS); + mediaInfo = getMediaInfo("contentId2", DURATION_2_MS); Mockito.when(status.getMediaInfo()).thenReturn(mediaInfo); TimelineAsserts.assertPeriodDurations( tracker.getCastTimeline(status), @@ -80,7 +79,7 @@ public class CastTimelineTrackerTest { DURATION_5_MS, MediaInfo.UNKNOWN_DURATION }); - mediaInfo = mockMediaInfo("contentId5", DURATION_5_MS); + mediaInfo = getMediaInfo("contentId5", DURATION_5_MS); Mockito.when(newStatus.getMediaInfo()).thenReturn(mediaInfo); TimelineAsserts.assertPeriodDurations( tracker.getCastTimeline(newStatus), @@ -89,7 +88,7 @@ public class CastTimelineTrackerTest { C.msToUs(DURATION_5_MS), C.msToUs(DURATION_3_MS)); - mediaInfo = mockMediaInfo("contentId3", DURATION_3_MS); + mediaInfo = getMediaInfo("contentId3", DURATION_3_MS); Mockito.when(newStatus.getMediaInfo()).thenReturn(mediaInfo); TimelineAsserts.assertPeriodDurations( tracker.getCastTimeline(newStatus), @@ -98,7 +97,7 @@ public class CastTimelineTrackerTest { C.msToUs(DURATION_5_MS), C.msToUs(DURATION_3_MS)); - mediaInfo = mockMediaInfo("contentId4", DURATION_4_MS); + mediaInfo = getMediaInfo("contentId4", DURATION_4_MS); Mockito.when(newStatus.getMediaInfo()).thenReturn(mediaInfo); TimelineAsserts.assertPeriodDurations( tracker.getCastTimeline(newStatus), @@ -112,7 +111,7 @@ public class CastTimelineTrackerTest { int[] itemIds, String[] contentIds, long[] durationsMs) { ArrayList items = new ArrayList<>(); for (int i = 0; i < contentIds.length; i++) { - MediaInfo mediaInfo = mockMediaInfo(contentIds[i], durationsMs[i]); + MediaInfo mediaInfo = getMediaInfo(contentIds[i], durationsMs[i]); MediaQueueItem item = Mockito.mock(MediaQueueItem.class); Mockito.when(item.getMedia()).thenReturn(mediaInfo); Mockito.when(item.getItemId()).thenReturn(itemIds[i]); @@ -123,10 +122,11 @@ public class CastTimelineTrackerTest { return status; } - private static MediaInfo mockMediaInfo(String contentId, long durationMs) { - MediaInfo mediaInfo = Mockito.mock(MediaInfo.class); - Mockito.when(mediaInfo.getContentId()).thenReturn(contentId); - Mockito.when(mediaInfo.getStreamDuration()).thenReturn(durationMs); - return mediaInfo; + private static MediaInfo getMediaInfo(String contentId, long durationMs) { + return new MediaInfo.Builder(contentId) + .setStreamDuration(durationMs) + .setContentType(MimeTypes.APPLICATION_MP4) + .setStreamType(MediaInfo.STREAM_TYPE_NONE) + .build(); } } diff --git a/extensions/cast/src/test/resources/robolectric.properties b/extensions/cast/src/test/resources/robolectric.properties new file mode 100644 index 0000000000..2f3210368e --- /dev/null +++ b/extensions/cast/src/test/resources/robolectric.properties @@ -0,0 +1 @@ +manifest=src/test/AndroidManifest.xml diff --git a/extensions/cronet/build.gradle b/extensions/cronet/build.gradle index 0b6f9a587c..1cfb4f5513 100644 --- a/extensions/cronet/build.gradle +++ b/extensions/cronet/build.gradle @@ -35,16 +35,13 @@ android { } dependencies { - compile project(modulePrefix + 'library-core') - compile files('libs/cronet_api.jar') - compile files('libs/cronet_impl_common_java.jar') - compile files('libs/cronet_impl_native_java.jar') - androidTestCompile project(modulePrefix + 'library') - androidTestCompile project(modulePrefix + 'testutils') - androidTestCompile 'com.google.dexmaker:dexmaker:' + dexmakerVersion - androidTestCompile 'com.google.dexmaker:dexmaker-mockito:' + dexmakerVersion - androidTestCompile 'org.mockito:mockito-core:' + mockitoVersion - androidTestCompile 'com.android.support.test:runner:' + testSupportLibraryVersion + api files('libs/cronet_api.jar') + implementation files('libs/cronet_impl_common_java.jar') + implementation files('libs/cronet_impl_native_java.jar') + implementation project(modulePrefix + 'library-core') + implementation 'com.android.support:support-annotations:' + supportLibraryVersion + testImplementation project(modulePrefix + 'library') + testImplementation project(modulePrefix + 'testutils-robolectric') } ext { diff --git a/extensions/cronet/src/androidTest/AndroidManifest.xml b/extensions/cronet/src/test/AndroidManifest.xml similarity index 71% rename from extensions/cronet/src/androidTest/AndroidManifest.xml rename to extensions/cronet/src/test/AndroidManifest.xml index 453cc68478..52be9aa157 100644 --- a/extensions/cronet/src/androidTest/AndroidManifest.xml +++ b/extensions/cronet/src/test/AndroidManifest.xml @@ -18,16 +18,6 @@ xmlns:tools="http://schemas.android.com/tools" package="com.google.android.exoplayer2.ext.cronet"> - - - - - - - + diff --git a/extensions/cronet/src/androidTest/java/com/google/android/exoplayer2/ext/cronet/ByteArrayUploadDataProviderTest.java b/extensions/cronet/src/test/java/com/google/android/exoplayer2/ext/cronet/ByteArrayUploadDataProviderTest.java similarity index 90% rename from extensions/cronet/src/androidTest/java/com/google/android/exoplayer2/ext/cronet/ByteArrayUploadDataProviderTest.java rename to extensions/cronet/src/test/java/com/google/android/exoplayer2/ext/cronet/ByteArrayUploadDataProviderTest.java index 28d22b91a5..291e73fcc1 100644 --- a/extensions/cronet/src/androidTest/java/com/google/android/exoplayer2/ext/cronet/ByteArrayUploadDataProviderTest.java +++ b/extensions/cronet/src/test/java/com/google/android/exoplayer2/ext/cronet/ByteArrayUploadDataProviderTest.java @@ -19,9 +19,6 @@ import static com.google.common.truth.Truth.assertThat; import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; -import android.support.test.InstrumentationRegistry; -import android.support.test.runner.AndroidJUnit4; -import com.google.android.exoplayer2.testutil.MockitoUtil; import java.io.IOException; import java.nio.ByteBuffer; import java.util.Arrays; @@ -30,11 +27,11 @@ import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; import org.mockito.Mock; +import org.mockito.MockitoAnnotations; +import org.robolectric.RobolectricTestRunner; -/** - * Tests for {@link ByteArrayUploadDataProvider}. - */ -@RunWith(AndroidJUnit4.class) +/** Tests for {@link ByteArrayUploadDataProvider}. */ +@RunWith(RobolectricTestRunner.class) public final class ByteArrayUploadDataProviderTest { private static final byte[] TEST_DATA = new byte[] {1, 2, 3, 4, 5, 6, 7, 8, 9, 10}; @@ -45,7 +42,7 @@ public final class ByteArrayUploadDataProviderTest { @Before public void setUp() { - MockitoUtil.setUpMockito(InstrumentationRegistry.getTargetContext(), this); + MockitoAnnotations.initMocks(this); byteBuffer = ByteBuffer.allocate(TEST_DATA.length); byteArrayUploadDataProvider = new ByteArrayUploadDataProvider(TEST_DATA); } @@ -90,5 +87,4 @@ public final class ByteArrayUploadDataProviderTest { assertThat(byteBuffer.array()).isEqualTo(TEST_DATA); verify(mockUploadDataSink).onRewindSucceeded(); } - } diff --git a/extensions/cronet/src/androidTest/java/com/google/android/exoplayer2/ext/cronet/CronetDataSourceTest.java b/extensions/cronet/src/test/java/com/google/android/exoplayer2/ext/cronet/CronetDataSourceTest.java similarity index 71% rename from extensions/cronet/src/androidTest/java/com/google/android/exoplayer2/ext/cronet/CronetDataSourceTest.java rename to extensions/cronet/src/test/java/com/google/android/exoplayer2/ext/cronet/CronetDataSourceTest.java index 79be44398e..4e990cd027 100644 --- a/extensions/cronet/src/androidTest/java/com/google/android/exoplayer2/ext/cronet/CronetDataSourceTest.java +++ b/extensions/cronet/src/test/java/com/google/android/exoplayer2/ext/cronet/CronetDataSourceTest.java @@ -31,10 +31,8 @@ import static org.mockito.Mockito.when; import android.net.Uri; import android.os.ConditionVariable; -import android.support.test.InstrumentationRegistry; -import android.support.test.runner.AndroidJUnit4; +import android.os.SystemClock; import com.google.android.exoplayer2.C; -import com.google.android.exoplayer2.testutil.MockitoUtil; import com.google.android.exoplayer2.upstream.DataSpec; import com.google.android.exoplayer2.upstream.HttpDataSource; import com.google.android.exoplayer2.upstream.HttpDataSource.HttpDataSourceException; @@ -50,6 +48,7 @@ import java.util.Arrays; import java.util.Collections; import java.util.HashMap; import java.util.Map; +import java.util.concurrent.CountDownLatch; import java.util.concurrent.Executor; import java.util.concurrent.atomic.AtomicInteger; import org.chromium.net.CronetEngine; @@ -61,13 +60,14 @@ import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; import org.mockito.Mock; +import org.mockito.MockitoAnnotations; import org.mockito.invocation.InvocationOnMock; import org.mockito.stubbing.Answer; +import org.robolectric.RobolectricTestRunner; +import org.robolectric.shadows.ShadowSystemClock; -/** - * Tests for {@link CronetDataSource}. - */ -@RunWith(AndroidJUnit4.class) +/** Tests for {@link CronetDataSource}. */ +@RunWith(RobolectricTestRunner.class) public final class CronetDataSourceTest { private static final int TEST_CONNECT_TIMEOUT_MS = 100; @@ -85,18 +85,11 @@ public final class CronetDataSourceTest { private UrlResponseInfo testUrlResponseInfo; @Mock private UrlRequest.Builder mockUrlRequestBuilder; - @Mock - private UrlRequest mockUrlRequest; - @Mock - private Predicate mockContentTypePredicate; - @Mock - private TransferListener mockTransferListener; - @Mock - private Clock mockClock; - @Mock - private Executor mockExecutor; - @Mock - private NetworkException mockNetworkException; + @Mock private UrlRequest mockUrlRequest; + @Mock private Predicate mockContentTypePredicate; + @Mock private TransferListener mockTransferListener; + @Mock private Executor mockExecutor; + @Mock private NetworkException mockNetworkException; @Mock private CronetEngine mockCronetEngine; private CronetDataSource dataSourceUnderTest; @@ -104,30 +97,31 @@ public final class CronetDataSourceTest { @Before public void setUp() throws Exception { - MockitoUtil.setUpMockito(InstrumentationRegistry.getTargetContext(), this); - dataSourceUnderTest = spy( - new CronetDataSource( - mockCronetEngine, - mockExecutor, - mockContentTypePredicate, - mockTransferListener, - TEST_CONNECT_TIMEOUT_MS, - TEST_READ_TIMEOUT_MS, - true, // resetTimeoutOnRedirects - mockClock, - null, - false)); + MockitoAnnotations.initMocks(this); + dataSourceUnderTest = + spy( + new CronetDataSource( + mockCronetEngine, + mockExecutor, + mockContentTypePredicate, + mockTransferListener, + TEST_CONNECT_TIMEOUT_MS, + TEST_READ_TIMEOUT_MS, + true, // resetTimeoutOnRedirects + Clock.DEFAULT, + null, + false)); when(mockContentTypePredicate.evaluate(anyString())).thenReturn(true); when(mockCronetEngine.newUrlRequestBuilder( - anyString(), any(UrlRequest.Callback.class), any(Executor.class))) + anyString(), any(UrlRequest.Callback.class), any(Executor.class))) .thenReturn(mockUrlRequestBuilder); when(mockUrlRequestBuilder.allowDirectExecutor()).thenReturn(mockUrlRequestBuilder); when(mockUrlRequestBuilder.build()).thenReturn(mockUrlRequest); mockStatusResponse(); testDataSpec = new DataSpec(Uri.parse(TEST_URL), 0, C.LENGTH_UNSET, null); - testPostDataSpec = new DataSpec( - Uri.parse(TEST_URL), TEST_POST_BODY, 0, 0, C.LENGTH_UNSET, null, 0); + testPostDataSpec = + new DataSpec(Uri.parse(TEST_URL), TEST_POST_BODY, 0, 0, C.LENGTH_UNSET, null, 0); testResponseHeader = new HashMap<>(); testResponseHeader.put("Content-Type", TEST_CONTENT_TYPE); // This value can be anything since the DataSpec is unset. @@ -173,20 +167,19 @@ public final class CronetDataSourceTest { // Prepare a mock UrlRequest to be used in the second open() call. final UrlRequest mockUrlRequest2 = mock(UrlRequest.class); when(mockUrlRequestBuilder.build()).thenReturn(mockUrlRequest2); - doAnswer(new Answer() { - @Override - public Object answer(InvocationOnMock invocation) throws Throwable { - // Invoke the callback for the previous request. - dataSourceUnderTest.onFailed( - mockUrlRequest, - testUrlResponseInfo, - mockNetworkException); - dataSourceUnderTest.onResponseStarted( - mockUrlRequest2, - testUrlResponseInfo); - return null; - } - }).when(mockUrlRequest2).start(); + doAnswer( + new Answer() { + @Override + public Object answer(InvocationOnMock invocation) throws Throwable { + // Invoke the callback for the previous request. + dataSourceUnderTest.onFailed( + mockUrlRequest, testUrlResponseInfo, mockNetworkException); + dataSourceUnderTest.onResponseStarted(mockUrlRequest2, testUrlResponseInfo); + return null; + } + }) + .when(mockUrlRequest2) + .start(); dataSourceUnderTest.open(testDataSpec); } @@ -253,8 +246,8 @@ public final class CronetDataSourceTest { @Test public void testRequestOpenFailDueToDnsFailure() { mockResponseStartFailure(); - when(mockNetworkException.getErrorCode()).thenReturn( - NetworkException.ERROR_HOSTNAME_NOT_RESOLVED); + when(mockNetworkException.getErrorCode()) + .thenReturn(NetworkException.ERROR_HOSTNAME_NOT_RESOLVED); try { dataSourceUnderTest.open(testDataSpec); @@ -524,8 +517,8 @@ public final class CronetDataSourceTest { assertThat(bytesOverRead).isEqualTo(C.RESULT_END_OF_INPUT); assertThat(returnedBuffer).isEqualTo(new byte[16]); // C.RESULT_END_OF_INPUT should not be reported though the TransferListener. - verify(mockTransferListener, never()).onBytesTransferred(dataSourceUnderTest, - C.RESULT_END_OF_INPUT); + verify(mockTransferListener, never()) + .onBytesTransferred(dataSourceUnderTest, C.RESULT_END_OF_INPUT); // There should still be only one call to read on cronet. verify(mockUrlRequest, times(1)).read(any(ByteBuffer.class)); // Check for connection not automatically closed. @@ -534,10 +527,10 @@ public final class CronetDataSourceTest { } @Test - public void testConnectTimeout() { - when(mockClock.elapsedRealtime()).thenReturn(0L); + public void testConnectTimeout() throws InterruptedException { + long startTimeMs = SystemClock.elapsedRealtime(); final ConditionVariable startCondition = buildUrlRequestStartedCondition(); - final ConditionVariable timedOutCondition = new ConditionVariable(); + final CountDownLatch timedOutLatch = new CountDownLatch(1); new Thread() { @Override @@ -551,29 +544,29 @@ public final class CronetDataSourceTest { assertThat(e.getCause() instanceof SocketTimeoutException).isTrue(); assertThat(((CronetDataSource.OpenException) e).cronetConnectionStatus) .isEqualTo(TEST_CONNECTION_STATUS); - timedOutCondition.open(); + timedOutLatch.countDown(); } } }.start(); startCondition.block(); // We should still be trying to open. - assertThat(timedOutCondition.block(50)).isFalse(); + assertNotCountedDown(timedOutLatch); // We should still be trying to open as we approach the timeout. - when(mockClock.elapsedRealtime()).thenReturn((long) TEST_CONNECT_TIMEOUT_MS - 1); - assertThat(timedOutCondition.block(50)).isFalse(); + ShadowSystemClock.setCurrentTimeMillis(startTimeMs + TEST_CONNECT_TIMEOUT_MS - 1); + assertNotCountedDown(timedOutLatch); // Now we timeout. - when(mockClock.elapsedRealtime()).thenReturn((long) TEST_CONNECT_TIMEOUT_MS); - timedOutCondition.block(); + ShadowSystemClock.setCurrentTimeMillis(startTimeMs + TEST_CONNECT_TIMEOUT_MS + 10); + timedOutLatch.await(); verify(mockTransferListener, never()).onTransferStart(dataSourceUnderTest, testDataSpec); } @Test - public void testConnectInterrupted() { - when(mockClock.elapsedRealtime()).thenReturn(0L); + public void testConnectInterrupted() throws InterruptedException { + long startTimeMs = SystemClock.elapsedRealtime(); final ConditionVariable startCondition = buildUrlRequestStartedCondition(); - final ConditionVariable timedOutCondition = new ConditionVariable(); + final CountDownLatch timedOutLatch = new CountDownLatch(1); Thread thread = new Thread() { @@ -588,7 +581,7 @@ public final class CronetDataSourceTest { assertThat(e.getCause() instanceof CronetDataSource.InterruptedIOException).isTrue(); assertThat(((CronetDataSource.OpenException) e).cronetConnectionStatus) .isEqualTo(TEST_INVALID_CONNECTION_STATUS); - timedOutCondition.open(); + timedOutLatch.countDown(); } } }; @@ -596,29 +589,29 @@ public final class CronetDataSourceTest { startCondition.block(); // We should still be trying to open. - assertThat(timedOutCondition.block(50)).isFalse(); + assertNotCountedDown(timedOutLatch); // We should still be trying to open as we approach the timeout. - when(mockClock.elapsedRealtime()).thenReturn((long) TEST_CONNECT_TIMEOUT_MS - 1); - assertThat(timedOutCondition.block(50)).isFalse(); + ShadowSystemClock.setCurrentTimeMillis(startTimeMs + TEST_CONNECT_TIMEOUT_MS - 1); + assertNotCountedDown(timedOutLatch); // Now we interrupt. thread.interrupt(); - timedOutCondition.block(); + timedOutLatch.await(); verify(mockTransferListener, never()).onTransferStart(dataSourceUnderTest, testDataSpec); } @Test - public void testConnectResponseBeforeTimeout() { - when(mockClock.elapsedRealtime()).thenReturn(0L); + public void testConnectResponseBeforeTimeout() throws InterruptedException { + long startTimeMs = SystemClock.elapsedRealtime(); final ConditionVariable startCondition = buildUrlRequestStartedCondition(); - final ConditionVariable openCondition = new ConditionVariable(); + final CountDownLatch openLatch = new CountDownLatch(1); new Thread() { @Override public void run() { try { dataSourceUnderTest.open(testDataSpec); - openCondition.open(); + openLatch.countDown(); } catch (HttpDataSourceException e) { fail(); } @@ -627,20 +620,20 @@ public final class CronetDataSourceTest { startCondition.block(); // We should still be trying to open. - assertThat(openCondition.block(50)).isFalse(); + assertNotCountedDown(openLatch); // We should still be trying to open as we approach the timeout. - when(mockClock.elapsedRealtime()).thenReturn((long) TEST_CONNECT_TIMEOUT_MS - 1); - assertThat(openCondition.block(50)).isFalse(); + ShadowSystemClock.setCurrentTimeMillis(startTimeMs + TEST_CONNECT_TIMEOUT_MS - 1); + assertNotCountedDown(openLatch); // The response arrives just in time. dataSourceUnderTest.onResponseStarted(mockUrlRequest, testUrlResponseInfo); - openCondition.block(); + openLatch.await(); } @Test public void testRedirectIncreasesConnectionTimeout() throws InterruptedException { - when(mockClock.elapsedRealtime()).thenReturn(0L); + long startTimeMs = SystemClock.elapsedRealtime(); final ConditionVariable startCondition = buildUrlRequestStartedCondition(); - final ConditionVariable timedOutCondition = new ConditionVariable(); + final CountDownLatch timedOutLatch = new CountDownLatch(1); final AtomicInteger openExceptions = new AtomicInteger(0); new Thread() { @@ -654,40 +647,36 @@ public final class CronetDataSourceTest { assertThat(e instanceof CronetDataSource.OpenException).isTrue(); assertThat(e.getCause() instanceof SocketTimeoutException).isTrue(); openExceptions.getAndIncrement(); - timedOutCondition.open(); + timedOutLatch.countDown(); } } }.start(); startCondition.block(); // We should still be trying to open. - assertThat(timedOutCondition.block(50)).isFalse(); + assertNotCountedDown(timedOutLatch); // We should still be trying to open as we approach the timeout. - when(mockClock.elapsedRealtime()).thenReturn((long) TEST_CONNECT_TIMEOUT_MS - 1); - assertThat(timedOutCondition.block(50)).isFalse(); + ShadowSystemClock.setCurrentTimeMillis(startTimeMs + TEST_CONNECT_TIMEOUT_MS - 1); + assertNotCountedDown(timedOutLatch); // A redirect arrives just in time. - dataSourceUnderTest.onRedirectReceived(mockUrlRequest, testUrlResponseInfo, - "RandomRedirectedUrl1"); + dataSourceUnderTest.onRedirectReceived( + mockUrlRequest, testUrlResponseInfo, "RandomRedirectedUrl1"); long newTimeoutMs = 2 * TEST_CONNECT_TIMEOUT_MS - 1; - when(mockClock.elapsedRealtime()).thenReturn(newTimeoutMs - 1); - // Give the thread some time to run. - assertThat(timedOutCondition.block(newTimeoutMs)).isFalse(); + ShadowSystemClock.setCurrentTimeMillis(startTimeMs + newTimeoutMs - 1); // We should still be trying to open as we approach the new timeout. - assertThat(timedOutCondition.block(50)).isFalse(); + assertNotCountedDown(timedOutLatch); // A redirect arrives just in time. - dataSourceUnderTest.onRedirectReceived(mockUrlRequest, testUrlResponseInfo, - "RandomRedirectedUrl2"); + dataSourceUnderTest.onRedirectReceived( + mockUrlRequest, testUrlResponseInfo, "RandomRedirectedUrl2"); newTimeoutMs = 3 * TEST_CONNECT_TIMEOUT_MS - 2; - when(mockClock.elapsedRealtime()).thenReturn(newTimeoutMs - 1); - // Give the thread some time to run. - assertThat(timedOutCondition.block(newTimeoutMs)).isFalse(); + ShadowSystemClock.setCurrentTimeMillis(startTimeMs + newTimeoutMs - 1); // We should still be trying to open as we approach the new timeout. - assertThat(timedOutCondition.block(50)).isFalse(); + assertNotCountedDown(timedOutLatch); // Now we timeout. - when(mockClock.elapsedRealtime()).thenReturn(newTimeoutMs); - timedOutCondition.block(); + ShadowSystemClock.setCurrentTimeMillis(startTimeMs + newTimeoutMs + 10); + timedOutLatch.await(); verify(mockTransferListener, never()).onTransferStart(dataSourceUnderTest, testDataSpec); assertThat(openExceptions.get()).isEqualTo(1); @@ -707,20 +696,22 @@ public final class CronetDataSourceTest { } @Test - public void testRedirectParseAndAttachCookie_dataSourceHandlesSetCookie_andPreservesOriginalRequestHeaders() - throws HttpDataSourceException { - dataSourceUnderTest = spy( - new CronetDataSource( - mockCronetEngine, - mockExecutor, - mockContentTypePredicate, - mockTransferListener, - TEST_CONNECT_TIMEOUT_MS, - TEST_READ_TIMEOUT_MS, - true, // resetTimeoutOnRedirects - mockClock, - null, - true)); + public void + testRedirectParseAndAttachCookie_dataSourceHandlesSetCookie_andPreservesOriginalRequestHeaders() + throws HttpDataSourceException { + dataSourceUnderTest = + spy( + new CronetDataSource( + mockCronetEngine, + mockExecutor, + mockContentTypePredicate, + mockTransferListener, + TEST_CONNECT_TIMEOUT_MS, + TEST_READ_TIMEOUT_MS, + true, // resetTimeoutOnRedirects + Clock.DEFAULT, + null, + true)); dataSourceUnderTest.setRequestProperty("Content-Type", TEST_CONTENT_TYPE); mockSingleRedirectSuccess(); @@ -736,21 +727,23 @@ public final class CronetDataSourceTest { } @Test - public void testRedirectParseAndAttachCookie_dataSourceHandlesSetCookie_andPreservesOriginalRequestHeadersIncludingByteRangeHeader() - throws HttpDataSourceException { + public void + testRedirectParseAndAttachCookie_dataSourceHandlesSetCookie_andPreservesOriginalRequestHeadersIncludingByteRangeHeader() + throws HttpDataSourceException { testDataSpec = new DataSpec(Uri.parse(TEST_URL), 1000, 5000, null); - dataSourceUnderTest = spy( - new CronetDataSource( - mockCronetEngine, - mockExecutor, - mockContentTypePredicate, - mockTransferListener, - TEST_CONNECT_TIMEOUT_MS, - TEST_READ_TIMEOUT_MS, - true, // resetTimeoutOnRedirects - mockClock, - null, - true)); + dataSourceUnderTest = + spy( + new CronetDataSource( + mockCronetEngine, + mockExecutor, + mockContentTypePredicate, + mockTransferListener, + TEST_CONNECT_TIMEOUT_MS, + TEST_READ_TIMEOUT_MS, + true, // resetTimeoutOnRedirects + Clock.DEFAULT, + null, + true)); dataSourceUnderTest.setRequestProperty("Content-Type", TEST_CONTENT_TYPE); mockSingleRedirectSuccess(); @@ -778,18 +771,19 @@ public final class CronetDataSourceTest { @Test public void testRedirectNoSetCookieFollowsRedirect_dataSourceHandlesSetCookie() throws HttpDataSourceException { - dataSourceUnderTest = spy( - new CronetDataSource( - mockCronetEngine, - mockExecutor, - mockContentTypePredicate, - mockTransferListener, - TEST_CONNECT_TIMEOUT_MS, - TEST_READ_TIMEOUT_MS, - true, // resetTimeoutOnRedirects - mockClock, - null, - true)); + dataSourceUnderTest = + spy( + new CronetDataSource( + mockCronetEngine, + mockExecutor, + mockContentTypePredicate, + mockTransferListener, + TEST_CONNECT_TIMEOUT_MS, + TEST_READ_TIMEOUT_MS, + true, // resetTimeoutOnRedirects + Clock.DEFAULT, + null, + true)); mockSingleRedirectSuccess(); mockFollowRedirectSuccess(); @@ -804,8 +798,9 @@ public final class CronetDataSourceTest { // Make mockTransferListener throw an exception in CronetDataSource.close(). Ensure that // the subsequent open() call succeeds. - doThrow(new NullPointerException()).when(mockTransferListener).onTransferEnd( - dataSourceUnderTest); + doThrow(new NullPointerException()) + .when(mockTransferListener) + .onTransferEnd(dataSourceUnderTest); dataSourceUnderTest.open(testDataSpec); try { dataSourceUnderTest.close(); @@ -833,13 +828,12 @@ public final class CronetDataSourceTest { } @Test - public void testReadInterrupted() throws HttpDataSourceException { - when(mockClock.elapsedRealtime()).thenReturn(0L); + public void testReadInterrupted() throws HttpDataSourceException, InterruptedException { mockResponseStartSuccess(); dataSourceUnderTest.open(testDataSpec); final ConditionVariable startCondition = buildReadStartedCondition(); - final ConditionVariable timedOutCondition = new ConditionVariable(); + final CountDownLatch timedOutLatch = new CountDownLatch(1); byte[] returnedBuffer = new byte[8]; Thread thread = new Thread() { @@ -851,17 +845,17 @@ public final class CronetDataSourceTest { } catch (HttpDataSourceException e) { // Expected. assertThat(e.getCause() instanceof CronetDataSource.InterruptedIOException).isTrue(); - timedOutCondition.open(); + timedOutLatch.countDown(); } } }; thread.start(); startCondition.block(); - assertThat(timedOutCondition.block(50)).isFalse(); + assertNotCountedDown(timedOutLatch); // Now we interrupt. thread.interrupt(); - timedOutCondition.block(); + timedOutLatch.await(); } @Test @@ -876,122 +870,135 @@ public final class CronetDataSourceTest { // Helper methods. private void mockStatusResponse() { - doAnswer(new Answer() { - @Override - public Object answer(InvocationOnMock invocation) throws Throwable { - UrlRequest.StatusListener statusListener = - (UrlRequest.StatusListener) invocation.getArguments()[0]; - statusListener.onStatus(TEST_CONNECTION_STATUS); - return null; - } - }).when(mockUrlRequest).getStatus(any(UrlRequest.StatusListener.class)); + doAnswer( + new Answer() { + @Override + public Object answer(InvocationOnMock invocation) throws Throwable { + UrlRequest.StatusListener statusListener = + (UrlRequest.StatusListener) invocation.getArguments()[0]; + statusListener.onStatus(TEST_CONNECTION_STATUS); + return null; + } + }) + .when(mockUrlRequest) + .getStatus(any(UrlRequest.StatusListener.class)); } private void mockResponseStartSuccess() { - doAnswer(new Answer() { - @Override - public Object answer(InvocationOnMock invocation) throws Throwable { - dataSourceUnderTest.onResponseStarted( - mockUrlRequest, - testUrlResponseInfo); - return null; - } - }).when(mockUrlRequest).start(); + doAnswer( + new Answer() { + @Override + public Object answer(InvocationOnMock invocation) throws Throwable { + dataSourceUnderTest.onResponseStarted(mockUrlRequest, testUrlResponseInfo); + return null; + } + }) + .when(mockUrlRequest) + .start(); } private void mockResponseStartRedirect() { - doAnswer(new Answer() { - @Override - public Object answer(InvocationOnMock invocation) throws Throwable { - dataSourceUnderTest.onRedirectReceived( - mockUrlRequest, - createUrlResponseInfo(307), // statusCode - "http://redirect.location.com"); - return null; - } - }).when(mockUrlRequest).start(); + doAnswer( + new Answer() { + @Override + public Object answer(InvocationOnMock invocation) throws Throwable { + dataSourceUnderTest.onRedirectReceived( + mockUrlRequest, + createUrlResponseInfo(307), // statusCode + "http://redirect.location.com"); + return null; + } + }) + .when(mockUrlRequest) + .start(); } private void mockSingleRedirectSuccess() { - doAnswer(new Answer() { - @Override - public Object answer(InvocationOnMock invocation) throws Throwable { - if (!redirectCalled) { - redirectCalled = true; - dataSourceUnderTest.onRedirectReceived( - mockUrlRequest, - createUrlResponseInfoWithUrl("http://example.com/video", 300), - "http://example.com/video/redirect"); - } else { - dataSourceUnderTest.onResponseStarted( - mockUrlRequest, - testUrlResponseInfo); - } - return null; - } - }).when(mockUrlRequest).start(); + doAnswer( + new Answer() { + @Override + public Object answer(InvocationOnMock invocation) throws Throwable { + if (!redirectCalled) { + redirectCalled = true; + dataSourceUnderTest.onRedirectReceived( + mockUrlRequest, + createUrlResponseInfoWithUrl("http://example.com/video", 300), + "http://example.com/video/redirect"); + } else { + dataSourceUnderTest.onResponseStarted(mockUrlRequest, testUrlResponseInfo); + } + return null; + } + }) + .when(mockUrlRequest) + .start(); } private void mockFollowRedirectSuccess() { - doAnswer(new Answer() { - @Override - public Object answer(InvocationOnMock invocation) throws Throwable { - dataSourceUnderTest.onResponseStarted( - mockUrlRequest, - testUrlResponseInfo); - return null; - } - }).when(mockUrlRequest).followRedirect(); + doAnswer( + new Answer() { + @Override + public Object answer(InvocationOnMock invocation) throws Throwable { + dataSourceUnderTest.onResponseStarted(mockUrlRequest, testUrlResponseInfo); + return null; + } + }) + .when(mockUrlRequest) + .followRedirect(); } private void mockResponseStartFailure() { - doAnswer(new Answer() { - @Override - public Object answer(InvocationOnMock invocation) throws Throwable { - dataSourceUnderTest.onFailed( - mockUrlRequest, - createUrlResponseInfo(500), // statusCode - mockNetworkException); - return null; - } - }).when(mockUrlRequest).start(); + doAnswer( + new Answer() { + @Override + public Object answer(InvocationOnMock invocation) throws Throwable { + dataSourceUnderTest.onFailed( + mockUrlRequest, + createUrlResponseInfo(500), // statusCode + mockNetworkException); + return null; + } + }) + .when(mockUrlRequest) + .start(); } private void mockReadSuccess(int position, int length) { final int[] positionAndRemaining = new int[] {position, length}; - doAnswer(new Answer() { - @Override - public Void answer(InvocationOnMock invocation) throws Throwable { - if (positionAndRemaining[1] == 0) { - dataSourceUnderTest.onSucceeded(mockUrlRequest, testUrlResponseInfo); - } else { - ByteBuffer inputBuffer = (ByteBuffer) invocation.getArguments()[0]; - int readLength = Math.min(positionAndRemaining[1], inputBuffer.remaining()); - inputBuffer.put(buildTestDataBuffer(positionAndRemaining[0], readLength)); - positionAndRemaining[0] += readLength; - positionAndRemaining[1] -= readLength; - dataSourceUnderTest.onReadCompleted( - mockUrlRequest, - testUrlResponseInfo, - inputBuffer); - } - return null; - } - }).when(mockUrlRequest).read(any(ByteBuffer.class)); + doAnswer( + new Answer() { + @Override + public Void answer(InvocationOnMock invocation) throws Throwable { + if (positionAndRemaining[1] == 0) { + dataSourceUnderTest.onSucceeded(mockUrlRequest, testUrlResponseInfo); + } else { + ByteBuffer inputBuffer = (ByteBuffer) invocation.getArguments()[0]; + int readLength = Math.min(positionAndRemaining[1], inputBuffer.remaining()); + inputBuffer.put(buildTestDataBuffer(positionAndRemaining[0], readLength)); + positionAndRemaining[0] += readLength; + positionAndRemaining[1] -= readLength; + dataSourceUnderTest.onReadCompleted( + mockUrlRequest, testUrlResponseInfo, inputBuffer); + } + return null; + } + }) + .when(mockUrlRequest) + .read(any(ByteBuffer.class)); } private void mockReadFailure() { doAnswer( - new Answer() { - @Override - public Object answer(InvocationOnMock invocation) throws Throwable { - dataSourceUnderTest.onFailed( - mockUrlRequest, - createUrlResponseInfo(500), // statusCode - mockNetworkException); - return null; - } - }) + new Answer() { + @Override + public Object answer(InvocationOnMock invocation) throws Throwable { + dataSourceUnderTest.onFailed( + mockUrlRequest, + createUrlResponseInfo(500), // statusCode + mockNetworkException); + return null; + } + }) .when(mockUrlRequest) .read(any(ByteBuffer.class)); } @@ -999,13 +1006,13 @@ public final class CronetDataSourceTest { private ConditionVariable buildReadStartedCondition() { final ConditionVariable startedCondition = new ConditionVariable(); doAnswer( - new Answer() { - @Override - public Object answer(InvocationOnMock invocation) throws Throwable { - startedCondition.open(); - return null; - } - }) + new Answer() { + @Override + public Object answer(InvocationOnMock invocation) throws Throwable { + startedCondition.open(); + return null; + } + }) .when(mockUrlRequest) .read(any(ByteBuffer.class)); return startedCondition; @@ -1013,16 +1020,26 @@ public final class CronetDataSourceTest { private ConditionVariable buildUrlRequestStartedCondition() { final ConditionVariable startedCondition = new ConditionVariable(); - doAnswer(new Answer() { - @Override - public Object answer(InvocationOnMock invocation) throws Throwable { - startedCondition.open(); - return null; - } - }).when(mockUrlRequest).start(); + doAnswer( + new Answer() { + @Override + public Object answer(InvocationOnMock invocation) throws Throwable { + startedCondition.open(); + return null; + } + }) + .when(mockUrlRequest) + .start(); return startedCondition; } + private void assertNotCountedDown(CountDownLatch countDownLatch) throws InterruptedException { + // We are asserting that another thread does not count down the latch. We therefore sleep some + // time to give the other thread the chance to fail this test. + Thread.sleep(50); + assertThat(countDownLatch.getCount()).isGreaterThan(0L); + } + private static byte[] buildTestDataArray(int position, int length) { return buildTestDataBuffer(position, length).array(); } @@ -1045,5 +1062,4 @@ public final class CronetDataSourceTest { testBuffer.flip(); return testBuffer; } - } diff --git a/extensions/cronet/src/test/resources/robolectric.properties b/extensions/cronet/src/test/resources/robolectric.properties new file mode 100644 index 0000000000..2f3210368e --- /dev/null +++ b/extensions/cronet/src/test/resources/robolectric.properties @@ -0,0 +1 @@ +manifest=src/test/AndroidManifest.xml diff --git a/extensions/ffmpeg/build.gradle b/extensions/ffmpeg/build.gradle index 9820818f3e..e2d3a08e36 100644 --- a/extensions/ffmpeg/build.gradle +++ b/extensions/ffmpeg/build.gradle @@ -31,7 +31,7 @@ android { } dependencies { - compile project(modulePrefix + 'library-core') + implementation project(modulePrefix + 'library-core') } ext { diff --git a/extensions/flac/build.gradle b/extensions/flac/build.gradle index 4d840d34ac..f617064ce5 100644 --- a/extensions/flac/build.gradle +++ b/extensions/flac/build.gradle @@ -31,8 +31,8 @@ android { } dependencies { - compile project(modulePrefix + 'library-core') - androidTestCompile project(modulePrefix + 'testutils') + implementation project(modulePrefix + 'library-core') + androidTestImplementation project(modulePrefix + 'testutils') } ext { diff --git a/extensions/gvr/README.md b/extensions/gvr/README.md index 250cf58c2f..5dab885436 100644 --- a/extensions/gvr/README.md +++ b/extensions/gvr/README.md @@ -12,10 +12,10 @@ of surround sound and ambisonic soundfields. The easiest way to use the extension is to add it as a gradle dependency: ```gradle -compile 'com.google.android.exoplayer:extension-gvr:rX.X.X' +implementation 'com.google.android.exoplayer:extension-gvr:2.X.X' ``` -where `rX.X.X` is the version, which must match the version of the ExoPlayer +where `2.X.X` is the version, which must match the version of the ExoPlayer library being used. Alternatively, you can clone the ExoPlayer repository and depend on the module diff --git a/extensions/gvr/build.gradle b/extensions/gvr/build.gradle index 8236024512..f146ba4df6 100644 --- a/extensions/gvr/build.gradle +++ b/extensions/gvr/build.gradle @@ -25,8 +25,8 @@ android { } dependencies { - compile project(modulePrefix + 'library-core') - compile 'com.google.vr:sdk-audio:1.80.0' + implementation project(modulePrefix + 'library-core') + implementation 'com.google.vr:sdk-audio:1.80.0' } ext { diff --git a/extensions/ima/README.md b/extensions/ima/README.md index a796ca8694..c5ef1af35f 100644 --- a/extensions/ima/README.md +++ b/extensions/ima/README.md @@ -12,10 +12,10 @@ alongside content. The easiest way to use the extension is to add it as a gradle dependency: ```gradle -compile 'com.google.android.exoplayer:extension-ima:rX.X.X' +implementation 'com.google.android.exoplayer:extension-ima:2.X.X' ``` -where `rX.X.X` is the version, which must match the version of the ExoPlayer +where `2.X.X` is the version, which must match the version of the ExoPlayer library being used. Alternatively, you can clone the ExoPlayer repository and depend on the module diff --git a/extensions/ima/build.gradle b/extensions/ima/build.gradle index 5038aaf5b9..3a20e378ae 100644 --- a/extensions/ima/build.gradle +++ b/extensions/ima/build.gradle @@ -26,7 +26,6 @@ android { } dependencies { - compile project(modulePrefix + 'library-core') // This dependency is necessary to force the supportLibraryVersion of // com.android.support:support-v4 to be used. Else an older version (25.2.0) // is included via: @@ -34,14 +33,10 @@ dependencies { // |-- com.google.android.gms:play-services-ads-lite:11.4.2 // |-- com.google.android.gms:play-services-basement:11.4.2 // |-- com.android.support:support-v4:25.2.0 - compile 'com.android.support:support-v4:' + supportLibraryVersion - compile 'com.google.ads.interactivemedia.v3:interactivemedia:3.7.4' - compile 'com.google.android.gms:play-services-ads:' + playServicesLibraryVersion - androidTestCompile project(modulePrefix + 'library') - androidTestCompile 'com.google.dexmaker:dexmaker:' + dexmakerVersion - androidTestCompile 'com.google.dexmaker:dexmaker-mockito:' + dexmakerVersion - androidTestCompile 'org.mockito:mockito-core:' + mockitoVersion - androidTestCompile 'com.android.support.test:runner:' + testSupportLibraryVersion + api 'com.android.support:support-v4:' + supportLibraryVersion + api 'com.google.ads.interactivemedia.v3:interactivemedia:3.7.4' + implementation project(modulePrefix + 'library-core') + implementation 'com.google.android.gms:play-services-ads:' + playServicesLibraryVersion } ext { diff --git a/extensions/ima/src/main/java/com/google/android/exoplayer2/ext/ima/ImaAdsMediaSource.java b/extensions/ima/src/main/java/com/google/android/exoplayer2/ext/ima/ImaAdsMediaSource.java index 981e8352e0..1899c815da 100644 --- a/extensions/ima/src/main/java/com/google/android/exoplayer2/ext/ima/ImaAdsMediaSource.java +++ b/extensions/ima/src/main/java/com/google/android/exoplayer2/ext/ima/ImaAdsMediaSource.java @@ -20,12 +20,12 @@ import android.support.annotation.Nullable; import android.view.ViewGroup; import com.google.android.exoplayer2.ExoPlayer; import com.google.android.exoplayer2.Timeline; -import com.google.android.exoplayer2.source.CompositeMediaSource; import com.google.android.exoplayer2.source.MediaPeriod; import com.google.android.exoplayer2.source.MediaSource; import com.google.android.exoplayer2.source.ads.AdsMediaSource; import com.google.android.exoplayer2.upstream.Allocator; import com.google.android.exoplayer2.upstream.DataSource; +import java.io.IOException; /** * A {@link MediaSource} that inserts ads linearly with a provided content media source. @@ -33,10 +33,9 @@ import com.google.android.exoplayer2.upstream.DataSource; * @deprecated Use com.google.android.exoplayer2.source.ads.AdsMediaSource with ImaAdsLoader. */ @Deprecated -public final class ImaAdsMediaSource extends CompositeMediaSource { +public final class ImaAdsMediaSource implements MediaSource { private final AdsMediaSource adsMediaSource; - private Listener listener; /** * Constructs a new source that inserts ads linearly with the content specified by @@ -75,10 +74,23 @@ public final class ImaAdsMediaSource extends CompositeMediaSource { } @Override - public void prepareSource(ExoPlayer player, boolean isTopLevelSource, Listener listener) { - super.prepareSource(player, isTopLevelSource, listener); - this.listener = listener; - prepareChildSource(/* id= */ null, adsMediaSource); + public void prepareSource( + final ExoPlayer player, boolean isTopLevelSource, final Listener listener) { + adsMediaSource.prepareSource( + player, + isTopLevelSource, + new Listener() { + @Override + public void onSourceInfoRefreshed( + MediaSource source, Timeline timeline, @Nullable Object manifest) { + listener.onSourceInfoRefreshed(ImaAdsMediaSource.this, timeline, manifest); + } + }); + } + + @Override + public void maybeThrowSourceInfoRefreshError() throws IOException { + adsMediaSource.maybeThrowSourceInfoRefreshError(); } @Override @@ -92,8 +104,7 @@ public final class ImaAdsMediaSource extends CompositeMediaSource { } @Override - protected void onChildSourceInfoRefreshed( - Void id, MediaSource mediaSource, Timeline timeline, @Nullable Object manifest) { - listener.onSourceInfoRefreshed(this, timeline, manifest); + public void releaseSource() { + adsMediaSource.releaseSource(); } } diff --git a/extensions/leanback/README.md b/extensions/leanback/README.md index 1fa71c9a8c..4eba6552e1 100644 --- a/extensions/leanback/README.md +++ b/extensions/leanback/README.md @@ -11,10 +11,10 @@ ExoPlayer. The easiest way to use the extension is to add it as a gradle dependency: ```gradle -compile 'com.google.android.exoplayer:extension-leanback:rX.X.X' +implementation 'com.google.android.exoplayer:extension-leanback:2.X.X' ``` -where `rX.X.X` is the version, which must match the version of the ExoPlayer +where `2.X.X` is the version, which must match the version of the ExoPlayer library being used. Alternatively, you can clone the ExoPlayer repository and depend on the module diff --git a/extensions/leanback/build.gradle b/extensions/leanback/build.gradle index d8952ca2b8..dc187a5709 100644 --- a/extensions/leanback/build.gradle +++ b/extensions/leanback/build.gradle @@ -25,8 +25,8 @@ android { } dependencies { - compile project(modulePrefix + 'library-core') - compile('com.android.support:leanback-v17:' + supportLibraryVersion) + implementation project(modulePrefix + 'library-core') + implementation('com.android.support:leanback-v17:' + supportLibraryVersion) } ext { diff --git a/extensions/mediasession/README.md b/extensions/mediasession/README.md index 3278e8dba5..bd6b59c0c1 100644 --- a/extensions/mediasession/README.md +++ b/extensions/mediasession/README.md @@ -12,10 +12,10 @@ behaviour can be extended to support other playback and custom actions. The easiest way to use the extension is to add it as a gradle dependency: ```gradle -compile 'com.google.android.exoplayer:extension-mediasession:rX.X.X' +implementation 'com.google.android.exoplayer:extension-mediasession:2.X.X' ``` -where `rX.X.X` is the version, which must match the version of the ExoPlayer +where `2.X.X` is the version, which must match the version of the ExoPlayer library being used. Alternatively, you can clone the ExoPlayer repository and depend on the module diff --git a/extensions/mediasession/build.gradle b/extensions/mediasession/build.gradle index 651bd952f8..eaaf078b5c 100644 --- a/extensions/mediasession/build.gradle +++ b/extensions/mediasession/build.gradle @@ -25,8 +25,8 @@ android { } dependencies { - compile project(modulePrefix + 'library-core') - compile 'com.android.support:support-media-compat:' + supportLibraryVersion + implementation project(modulePrefix + 'library-core') + implementation 'com.android.support:support-media-compat:' + supportLibraryVersion } ext { diff --git a/extensions/mediasession/src/main/java/com/google/android/exoplayer2/ext/mediasession/MediaSessionConnector.java b/extensions/mediasession/src/main/java/com/google/android/exoplayer2/ext/mediasession/MediaSessionConnector.java index 2b4409e0fb..544644d03b 100644 --- a/extensions/mediasession/src/main/java/com/google/android/exoplayer2/ext/mediasession/MediaSessionConnector.java +++ b/extensions/mediasession/src/main/java/com/google/android/exoplayer2/ext/mediasession/MediaSessionConnector.java @@ -105,23 +105,24 @@ public final class MediaSessionConnector { */ public interface PlaybackPreparer extends CommandReceiver { - long ACTIONS = PlaybackStateCompat.ACTION_PREPARE - | PlaybackStateCompat.ACTION_PREPARE_FROM_MEDIA_ID - | PlaybackStateCompat.ACTION_PREPARE_FROM_SEARCH - | PlaybackStateCompat.ACTION_PREPARE_FROM_URI - | PlaybackStateCompat.ACTION_PLAY_FROM_MEDIA_ID - | PlaybackStateCompat.ACTION_PLAY_FROM_SEARCH - | PlaybackStateCompat.ACTION_PLAY_FROM_URI; + long ACTIONS = + PlaybackStateCompat.ACTION_PREPARE + | PlaybackStateCompat.ACTION_PREPARE_FROM_MEDIA_ID + | PlaybackStateCompat.ACTION_PREPARE_FROM_SEARCH + | PlaybackStateCompat.ACTION_PREPARE_FROM_URI + | PlaybackStateCompat.ACTION_PLAY_FROM_MEDIA_ID + | PlaybackStateCompat.ACTION_PLAY_FROM_SEARCH + | PlaybackStateCompat.ACTION_PLAY_FROM_URI; /** * Returns the actions which are supported by the preparer. The supported actions must be a - * bitmask combined out of {@link PlaybackStateCompat#ACTION_PREPARE}, - * {@link PlaybackStateCompat#ACTION_PREPARE_FROM_MEDIA_ID}, - * {@link PlaybackStateCompat#ACTION_PREPARE_FROM_SEARCH}, - * {@link PlaybackStateCompat#ACTION_PREPARE_FROM_URI}, - * {@link PlaybackStateCompat#ACTION_PLAY_FROM_MEDIA_ID}, - * {@link PlaybackStateCompat#ACTION_PLAY_FROM_SEARCH} and - * {@link PlaybackStateCompat#ACTION_PLAY_FROM_URI}. + * bitmask combined out of {@link PlaybackStateCompat#ACTION_PREPARE}, {@link + * PlaybackStateCompat#ACTION_PREPARE_FROM_MEDIA_ID}, {@link + * PlaybackStateCompat#ACTION_PREPARE_FROM_SEARCH}, {@link + * PlaybackStateCompat#ACTION_PREPARE_FROM_URI}, {@link + * PlaybackStateCompat#ACTION_PLAY_FROM_MEDIA_ID}, {@link + * PlaybackStateCompat#ACTION_PLAY_FROM_SEARCH} and {@link + * PlaybackStateCompat#ACTION_PLAY_FROM_URI}. * * @return The bitmask of the supported media actions. */ @@ -264,15 +265,6 @@ public final class MediaSessionConnector { */ public interface QueueEditor extends CommandReceiver { - long ACTIONS = PlaybackStateCompat.ACTION_SET_RATING; - - /** - * Returns {@link PlaybackStateCompat#ACTION_SET_RATING} or {@code 0}. The Media API does - * not declare action constants for adding and removing queue items. - * - * @param player The {@link Player}. - */ - long getSupportedQueueEditorActions(@Nullable Player player); /** * See {@link MediaSessionCompat.Callback#onAddQueueItem(MediaDescriptionCompat description)}. */ @@ -291,9 +283,14 @@ public final class MediaSessionConnector { * See {@link MediaSessionCompat.Callback#onRemoveQueueItemAt(int index)}. */ void onRemoveQueueItemAt(Player player, int index); - /** - * See {@link MediaSessionCompat.Callback#onSetRating(RatingCompat)}. - */ + } + + /** Callback receiving a user rating for the active media item. */ + public interface RatingCallback extends CommandReceiver { + + long ACTIONS = PlaybackStateCompat.ACTION_SET_RATING; + + /** See {@link MediaSessionCompat.Callback#onSetRating(RatingCompat)}. */ void onSetRating(Player player, RatingCompat rating); } @@ -341,6 +338,7 @@ public final class MediaSessionConnector { private PlaybackPreparer playbackPreparer; private QueueNavigator queueNavigator; private QueueEditor queueEditor; + private RatingCallback ratingCallback; private ExoPlaybackException playbackException; /** @@ -471,6 +469,17 @@ public final class MediaSessionConnector { : EDITOR_MEDIA_SESSION_FLAGS); } + /** + * Sets the {@link RatingCallback} to handle user ratings. + * + * @param ratingCallback The rating callback. + */ + public void setRatingCallback(RatingCallback ratingCallback) { + unregisterCommandReceiver(this.ratingCallback); + this.ratingCallback = ratingCallback; + registerCommandReceiver(this.ratingCallback); + } + private void registerCommandReceiver(CommandReceiver commandReceiver) { if (commandReceiver != null && commandReceiver.getCommands() != null) { for (String command : commandReceiver.getCommands()) { @@ -539,8 +548,8 @@ public final class MediaSessionConnector { actions |= (QueueNavigator.ACTIONS & queueNavigator.getSupportedQueueNavigatorActions( player)); } - if (queueEditor != null) { - actions |= (QueueEditor.ACTIONS & queueEditor.getSupportedQueueEditorActions(player)); + if (ratingCallback != null) { + actions |= RatingCallback.ACTIONS; } return actions; } @@ -634,6 +643,10 @@ public final class MediaSessionConnector { & PlaybackPreparer.ACTIONS & action) != 0; } + private boolean canDispatchToRatingCallback(long action) { + return ratingCallback != null && (RatingCallback.ACTIONS & action) != 0; + } + private boolean canDispatchToPlaybackController(long action) { return (playbackController.getSupportedPlaybackActions(player) & PlaybackController.ACTIONS & action) != 0; @@ -644,11 +657,6 @@ public final class MediaSessionConnector { & QueueNavigator.ACTIONS & action) != 0; } - private boolean canDispatchToQueueEditor(long action) { - return queueEditor != null && (queueEditor.getSupportedQueueEditorActions(player) - & QueueEditor.ACTIONS & action) != 0; - } - private class ExoPlayerEventListener extends Player.DefaultEventListener { private int currentWindowIndex; @@ -879,6 +887,13 @@ public final class MediaSessionConnector { } } + @Override + public void onSetRating(RatingCompat rating) { + if (canDispatchToRatingCallback(PlaybackStateCompat.ACTION_SET_RATING)) { + ratingCallback.onSetRating(player, rating); + } + } + @Override public void onAddQueueItem(MediaDescriptionCompat description) { if (queueEditor != null) { @@ -907,13 +922,6 @@ public final class MediaSessionConnector { } } - @Override - public void onSetRating(RatingCompat rating) { - if (canDispatchToQueueEditor(PlaybackStateCompat.ACTION_SET_RATING)) { - queueEditor.onSetRating(player, rating); - } - } - } } diff --git a/extensions/mediasession/src/main/java/com/google/android/exoplayer2/ext/mediasession/TimelineQueueEditor.java b/extensions/mediasession/src/main/java/com/google/android/exoplayer2/ext/mediasession/TimelineQueueEditor.java index 65090a3c1c..402abf7c70 100644 --- a/extensions/mediasession/src/main/java/com/google/android/exoplayer2/ext/mediasession/TimelineQueueEditor.java +++ b/extensions/mediasession/src/main/java/com/google/android/exoplayer2/ext/mediasession/TimelineQueueEditor.java @@ -20,7 +20,6 @@ import android.os.ResultReceiver; import android.support.annotation.NonNull; import android.support.annotation.Nullable; import android.support.v4.media.MediaDescriptionCompat; -import android.support.v4.media.RatingCompat; import android.support.v4.media.session.MediaControllerCompat; import android.support.v4.media.session.MediaSessionCompat; import com.google.android.exoplayer2.C; @@ -164,11 +163,6 @@ public final class TimelineQueueEditor implements MediaSessionConnector.QueueEdi this.equalityChecker = equalityChecker; } - @Override - public long getSupportedQueueEditorActions(@Nullable Player player) { - return 0; - } - @Override public void onAddQueueItem(Player player, MediaDescriptionCompat description) { onAddQueueItem(player, description, player.getCurrentTimeline().getWindowCount()); @@ -200,11 +194,6 @@ public final class TimelineQueueEditor implements MediaSessionConnector.QueueEdi queueMediaSource.removeMediaSource(index); } - @Override - public void onSetRating(Player player, RatingCompat rating) { - // Do nothing. - } - // CommandReceiver implementation. @NonNull diff --git a/extensions/okhttp/README.md b/extensions/okhttp/README.md index e40535d4e8..73297b54a9 100644 --- a/extensions/okhttp/README.md +++ b/extensions/okhttp/README.md @@ -19,10 +19,10 @@ licensed separately. The easiest way to use the extension is to add it as a gradle dependency: ```gradle -compile 'com.google.android.exoplayer:extension-okhttp:rX.X.X' +implementation 'com.google.android.exoplayer:extension-okhttp:2.X.X' ``` -where `rX.X.X` is the version, which must match the version of the ExoPlayer +where `2.X.X` is the version, which must match the version of the ExoPlayer library being used. Alternatively, you can clone the ExoPlayer repository and depend on the module diff --git a/extensions/okhttp/build.gradle b/extensions/okhttp/build.gradle index 13bcff8a4e..2da245b1a5 100644 --- a/extensions/okhttp/build.gradle +++ b/extensions/okhttp/build.gradle @@ -30,8 +30,9 @@ android { } dependencies { - compile project(modulePrefix + 'library-core') - compile('com.squareup.okhttp3:okhttp:3.9.0') { + implementation project(modulePrefix + 'library-core') + implementation 'com.android.support:support-annotations:' + supportLibraryVersion + implementation('com.squareup.okhttp3:okhttp:3.9.0') { exclude group: 'org.json' } } diff --git a/extensions/opus/build.gradle b/extensions/opus/build.gradle index 41b428070f..2d20c65697 100644 --- a/extensions/opus/build.gradle +++ b/extensions/opus/build.gradle @@ -31,7 +31,7 @@ android { } dependencies { - compile project(modulePrefix + 'library-core') + implementation project(modulePrefix + 'library-core') } ext { diff --git a/extensions/rtmp/README.md b/extensions/rtmp/README.md index fb822b8326..b222bdabd9 100644 --- a/extensions/rtmp/README.md +++ b/extensions/rtmp/README.md @@ -20,10 +20,10 @@ Android, which is licensed separately. The easiest way to use the extension is to add it as a gradle dependency: ```gradle -compile 'com.google.android.exoplayer:extension-rtmp:rX.X.X' +implementation 'com.google.android.exoplayer:extension-rtmp:2.X.X' ``` -where `rX.X.X` is the version, which must match the version of the ExoPlayer +where `2.X.X` is the version, which must match the version of the ExoPlayer library being used. Alternatively, you can clone the ExoPlayer repository and depend on the module diff --git a/extensions/rtmp/build.gradle b/extensions/rtmp/build.gradle index 2afa4a4ea7..c34e0b9999 100644 --- a/extensions/rtmp/build.gradle +++ b/extensions/rtmp/build.gradle @@ -25,8 +25,9 @@ android { } dependencies { - compile project(modulePrefix + 'library-core') - compile 'net.butterflytv.utils:rtmp-client:3.0.1' + implementation project(modulePrefix + 'library-core') + implementation 'net.butterflytv.utils:rtmp-client:3.0.1' + implementation 'com.android.support:support-annotations:' + supportLibraryVersion } ext { diff --git a/extensions/vp9/build.gradle b/extensions/vp9/build.gradle index 3d68e1428f..7dc95b388f 100644 --- a/extensions/vp9/build.gradle +++ b/extensions/vp9/build.gradle @@ -31,8 +31,9 @@ android { } dependencies { - compile project(modulePrefix + 'library-core') - androidTestCompile 'com.google.truth:truth:' + truthVersion + implementation project(modulePrefix + 'library-core') + implementation 'com.android.support:support-annotations:' + supportLibraryVersion + androidTestImplementation 'com.google.truth:truth:' + truthVersion } ext { diff --git a/extensions/vp9/src/main/java/com/google/android/exoplayer2/ext/vp9/LibvpxVideoRenderer.java b/extensions/vp9/src/main/java/com/google/android/exoplayer2/ext/vp9/LibvpxVideoRenderer.java index d93aa6d39e..4d75f6076b 100644 --- a/extensions/vp9/src/main/java/com/google/android/exoplayer2/ext/vp9/LibvpxVideoRenderer.java +++ b/extensions/vp9/src/main/java/com/google/android/exoplayer2/ext/vp9/LibvpxVideoRenderer.java @@ -119,7 +119,6 @@ public class LibvpxVideoRenderer extends BaseRenderer { private VpxDecoder decoder; private VpxInputBuffer inputBuffer; private VpxOutputBuffer outputBuffer; - private VpxOutputBuffer nextOutputBuffer; private DrmSession drmSession; private DrmSession pendingDrmSession; @@ -128,7 +127,6 @@ public class LibvpxVideoRenderer extends BaseRenderer { private Bitmap bitmap; private boolean renderedFirstFrame; - private boolean forceRenderFrame; private long joiningDeadlineMs; private Surface surface; private VpxOutputBufferRenderer outputBufferRenderer; @@ -144,6 +142,7 @@ public class LibvpxVideoRenderer extends BaseRenderer { private int droppedFrames; private int consecutiveDroppedFrameCount; private int buffersInCodecCount; + private long lastRenderTimeUs; protected DecoderCounters decoderCounters; @@ -254,7 +253,7 @@ public class LibvpxVideoRenderer extends BaseRenderer { try { // Rendering loop. TraceUtil.beginSection("drainAndFeed"); - while (drainOutputBuffer(positionUs)) {} + while (drainOutputBuffer(positionUs, elapsedRealtimeUs)) {} while (feedInputBuffer()) {} TraceUtil.endSection(); } catch (VpxDecoderException e) { @@ -319,6 +318,7 @@ public class LibvpxVideoRenderer extends BaseRenderer { protected void onStarted() { droppedFrames = 0; droppedFrameAccumulationStartTimeMs = SystemClock.elapsedRealtime(); + lastRenderTimeUs = SystemClock.elapsedRealtime() * 1000; } @Override @@ -379,7 +379,6 @@ public class LibvpxVideoRenderer extends BaseRenderer { @CallSuper protected void flushDecoder() throws ExoPlaybackException { waitingForKeys = false; - forceRenderFrame = false; buffersInCodecCount = 0; if (decoderReinitializationState != REINITIALIZATION_STATE_NONE) { releaseDecoder(); @@ -390,10 +389,6 @@ public class LibvpxVideoRenderer extends BaseRenderer { outputBuffer.release(); outputBuffer = null; } - if (nextOutputBuffer != null) { - nextOutputBuffer.release(); - nextOutputBuffer = null; - } decoder.flush(); decoderReceivedBuffers = false; } @@ -408,13 +403,11 @@ public class LibvpxVideoRenderer extends BaseRenderer { inputBuffer = null; outputBuffer = null; - nextOutputBuffer = null; decoder.release(); decoder = null; decoderCounters.decoderReleaseCount++; decoderReinitializationState = REINITIALIZATION_STATE_NONE; decoderReceivedBuffers = false; - forceRenderFrame = false; buffersInCodecCount = 0; } @@ -482,22 +475,15 @@ public class LibvpxVideoRenderer extends BaseRenderer { } /** - * Returns whether the current frame should be dropped. + * Returns whether the buffer being processed should be dropped. * - * @param outputBufferTimeUs The timestamp of the current output buffer. - * @param nextOutputBufferTimeUs The timestamp of the next output buffer or {@link C#TIME_UNSET} - * if the next output buffer is unavailable. - * @param positionUs The current playback position. - * @param joiningDeadlineMs The joining deadline. - * @return Returns whether to drop the current output buffer. + * @param earlyUs The time until the buffer should be presented in microseconds. A negative value + * indicates that the buffer is late. + * @param elapsedRealtimeUs {@link android.os.SystemClock#elapsedRealtime()} in microseconds, + * measured at the start of the current iteration of the rendering loop. */ - protected boolean shouldDropOutputBuffer( - long outputBufferTimeUs, - long nextOutputBufferTimeUs, - long positionUs, - long joiningDeadlineMs) { - return isBufferLate(outputBufferTimeUs - positionUs) - && (joiningDeadlineMs != C.TIME_UNSET || nextOutputBufferTimeUs != C.TIME_UNSET); + protected boolean shouldDropOutputBuffer(long earlyUs, long elapsedRealtimeUs) { + return isBufferLate(earlyUs); } /** @@ -506,11 +492,26 @@ public class LibvpxVideoRenderer extends BaseRenderer { * * @param earlyUs The time until the current buffer should be presented in microseconds. A * negative value indicates that the buffer is late. + * @param elapsedRealtimeUs {@link android.os.SystemClock#elapsedRealtime()} in microseconds, + * measured at the start of the current iteration of the rendering loop. */ - protected boolean shouldDropBuffersToKeyframe(long earlyUs) { + protected boolean shouldDropBuffersToKeyframe(long earlyUs, long elapsedRealtimeUs) { return isBufferVeryLate(earlyUs); } + /** + * Returns whether to force rendering an output buffer. + * + * @param earlyUs The time until the current buffer should be presented in microseconds. A + * negative value indicates that the buffer is late. + * @param elapsedSinceLastRenderUs The elapsed time since the last output buffer was rendered, in + * microseconds. + * @return Returns whether to force rendering an output buffer. + */ + protected boolean shouldForceRenderOutputBuffer(long earlyUs, long elapsedSinceLastRenderUs) { + return isBufferLate(earlyUs) && elapsedSinceLastRenderUs > 100000; + } + /** * Skips the specified output buffer and releases it. * @@ -543,6 +544,7 @@ public class LibvpxVideoRenderer extends BaseRenderer { int bufferMode = outputBuffer.mode; boolean renderRgb = bufferMode == VpxDecoder.OUTPUT_MODE_RGB && surface != null; boolean renderYuv = bufferMode == VpxDecoder.OUTPUT_MODE_YUV && outputBufferRenderer != null; + lastRenderTimeUs = SystemClock.elapsedRealtime() * 1000; if (!renderRgb && !renderYuv) { dropOutputBuffer(outputBuffer); } else { @@ -755,22 +757,18 @@ public class LibvpxVideoRenderer extends BaseRenderer { /** * Attempts to dequeue an output buffer from the decoder and, if successful, passes it to {@link - * #processOutputBuffer(long)}. + * #processOutputBuffer(long, long)}. * * @param positionUs The player's current position. + * @param elapsedRealtimeUs {@link android.os.SystemClock#elapsedRealtime()} in microseconds, + * measured at the start of the current iteration of the rendering loop. * @return Whether it may be possible to drain more output data. * @throws ExoPlaybackException If an error occurs draining the output buffer. */ - private boolean drainOutputBuffer(long positionUs) + private boolean drainOutputBuffer(long positionUs, long elapsedRealtimeUs) throws ExoPlaybackException, VpxDecoderException { - // Acquire outputBuffer either from nextOutputBuffer or from the decoder. if (outputBuffer == null) { - if (nextOutputBuffer != null) { - outputBuffer = nextOutputBuffer; - nextOutputBuffer = null; - } else { - outputBuffer = decoder.dequeueOutputBuffer(); - } + outputBuffer = decoder.dequeueOutputBuffer(); if (outputBuffer == null) { return false; } @@ -778,10 +776,6 @@ public class LibvpxVideoRenderer extends BaseRenderer { buffersInCodecCount -= outputBuffer.skippedOutputBufferCount; } - if (nextOutputBuffer == null) { - nextOutputBuffer = decoder.dequeueOutputBuffer(); - } - if (outputBuffer.isEndOfStream()) { if (decoderReinitializationState == REINITIALIZATION_STATE_WAIT_END_OF_STREAM) { // We're waiting to re-initialize the decoder, and have now processed all final buffers. @@ -795,7 +789,12 @@ public class LibvpxVideoRenderer extends BaseRenderer { return false; } - return processOutputBuffer(positionUs); + boolean processedOutputBuffer = processOutputBuffer(positionUs, elapsedRealtimeUs); + if (processedOutputBuffer) { + onProcessedOutputBuffer(outputBuffer.timeUs); + outputBuffer = null; + } + return processedOutputBuffer; } /** @@ -803,53 +802,47 @@ public class LibvpxVideoRenderer extends BaseRenderer { * whether it may be possible to process another output buffer. * * @param positionUs The player's current position. + * @param elapsedRealtimeUs {@link android.os.SystemClock#elapsedRealtime()} in microseconds, + * measured at the start of the current iteration of the rendering loop. * @return Whether it may be possible to drain another output buffer. * @throws ExoPlaybackException If an error occurs processing the output buffer. */ - private boolean processOutputBuffer(long positionUs) throws ExoPlaybackException { + private boolean processOutputBuffer(long positionUs, long elapsedRealtimeUs) + throws ExoPlaybackException { + long earlyUs = outputBuffer.timeUs - positionUs; if (outputMode == VpxDecoder.OUTPUT_MODE_NONE) { // Skip frames in sync with playback, so we'll be at the right frame if the mode changes. - if (isBufferLate(outputBuffer.timeUs - positionUs)) { - forceRenderFrame = false; + if (isBufferLate(earlyUs)) { skipOutputBuffer(outputBuffer); - onProcessedOutputBuffer(outputBuffer.timeUs); - outputBuffer = null; return true; } return false; } - if (forceRenderFrame) { - forceRenderFrame = false; + long elapsedRealtimeNowUs = SystemClock.elapsedRealtime() * 1000; + boolean isStarted = getState() == STATE_STARTED; + if (!renderedFirstFrame + || (isStarted + && shouldForceRenderOutputBuffer(earlyUs, elapsedRealtimeNowUs - lastRenderTimeUs))) { renderOutputBuffer(outputBuffer); - onProcessedOutputBuffer(outputBuffer.timeUs); - outputBuffer = null; return true; } - long nextOutputBufferTimeUs = - nextOutputBuffer != null && !nextOutputBuffer.isEndOfStream() - ? nextOutputBuffer.timeUs - : C.TIME_UNSET; - - long earlyUs = outputBuffer.timeUs - positionUs; - if (shouldDropBuffersToKeyframe(earlyUs) && maybeDropBuffersToKeyframe(positionUs)) { - forceRenderFrame = true; + if (!isStarted) { return false; - } else if (shouldDropOutputBuffer( - outputBuffer.timeUs, nextOutputBufferTimeUs, positionUs, joiningDeadlineMs)) { + } + + if (shouldDropBuffersToKeyframe(earlyUs, elapsedRealtimeUs) + && maybeDropBuffersToKeyframe(positionUs)) { + return false; + } else if (shouldDropOutputBuffer(earlyUs, elapsedRealtimeUs)) { dropOutputBuffer(outputBuffer); - onProcessedOutputBuffer(outputBuffer.timeUs); - outputBuffer = null; return true; } - // If we have yet to render a frame to the current output (either initially or immediately - // following a seek), render one irrespective of the state or current position. - if (!renderedFirstFrame || (getState() == STATE_STARTED && earlyUs <= 30000)) { + if (earlyUs < 30000) { renderOutputBuffer(outputBuffer); - onProcessedOutputBuffer(outputBuffer.timeUs); - outputBuffer = null; + return true; } return false; diff --git a/library/all/build.gradle b/library/all/build.gradle index 79ed9c747b..bb832ba0ff 100644 --- a/library/all/build.gradle +++ b/library/all/build.gradle @@ -25,11 +25,11 @@ android { } dependencies { - compile project(modulePrefix + 'library-core') - compile project(modulePrefix + 'library-dash') - compile project(modulePrefix + 'library-hls') - compile project(modulePrefix + 'library-smoothstreaming') - compile project(modulePrefix + 'library-ui') + api project(modulePrefix + 'library-core') + api project(modulePrefix + 'library-dash') + api project(modulePrefix + 'library-hls') + api project(modulePrefix + 'library-smoothstreaming') + api project(modulePrefix + 'library-ui') } ext { diff --git a/library/core/build.gradle b/library/core/build.gradle index a87e11065f..fe6045c2e7 100644 --- a/library/core/build.gradle +++ b/library/core/build.gradle @@ -31,6 +31,7 @@ android { } test { java.srcDirs += "../../testutils/src/main/java/" + java.srcDirs += "../../testutils_robolectric/src/main/java/" } } @@ -44,15 +45,15 @@ android { } dependencies { - compile 'com.android.support:support-annotations:' + supportLibraryVersion - androidTestCompile 'com.google.dexmaker:dexmaker:' + dexmakerVersion - androidTestCompile 'com.google.dexmaker:dexmaker-mockito:' + dexmakerVersion - androidTestCompile 'com.google.truth:truth:' + truthVersion - androidTestCompile 'org.mockito:mockito-core:' + mockitoVersion - testCompile 'com.google.truth:truth:' + truthVersion - testCompile 'junit:junit:' + junitVersion - testCompile 'org.mockito:mockito-core:' + mockitoVersion - testCompile 'org.robolectric:robolectric:' + robolectricVersion + implementation 'com.android.support:support-annotations:' + supportLibraryVersion + androidTestImplementation 'com.google.dexmaker:dexmaker:' + dexmakerVersion + androidTestImplementation 'com.google.dexmaker:dexmaker-mockito:' + dexmakerVersion + androidTestImplementation 'com.google.truth:truth:' + truthVersion + androidTestImplementation 'org.mockito:mockito-core:' + mockitoVersion + testImplementation 'com.google.truth:truth:' + truthVersion + testImplementation 'junit:junit:' + junitVersion + testImplementation 'org.mockito:mockito-core:' + mockitoVersion + testImplementation 'org.robolectric:robolectric:' + robolectricVersion } ext { diff --git a/library/core/src/androidTest/java/com/google/android/exoplayer2/upstream/ContentDataSourceTest.java b/library/core/src/androidTest/java/com/google/android/exoplayer2/upstream/ContentDataSourceTest.java index 83a978219e..3465393853 100644 --- a/library/core/src/androidTest/java/com/google/android/exoplayer2/upstream/ContentDataSourceTest.java +++ b/library/core/src/androidTest/java/com/google/android/exoplayer2/upstream/ContentDataSourceTest.java @@ -89,7 +89,7 @@ public final class ContentDataSourceTest extends InstrumentationTestCase { ContentDataSource dataSource = new ContentDataSource(instrumentation.getContext()); try { DataSpec dataSpec = new DataSpec(contentUri, offset, length, null); - byte[] completeData = TestUtil.getByteArray(instrumentation, DATA_PATH); + byte[] completeData = TestUtil.getByteArray(instrumentation.getContext(), DATA_PATH); byte[] expectedData = Arrays.copyOfRange(completeData, offset, length == C.LENGTH_UNSET ? completeData.length : offset + length); TestUtil.assertDataSourceContent(dataSource, dataSpec, expectedData, !pipeMode); diff --git a/library/core/src/main/java/com/google/android/exoplayer2/ExoPlayerImpl.java b/library/core/src/main/java/com/google/android/exoplayer2/ExoPlayerImpl.java index 83bbdd1157..0e0a6e3c26 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/ExoPlayerImpl.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/ExoPlayerImpl.java @@ -170,7 +170,7 @@ import java.util.concurrent.CopyOnWriteArraySet; // because it uses a callback. hasPendingPrepare = true; pendingOperationAcks++; - internalPlayer.prepare(mediaSource, resetPosition); + internalPlayer.prepare(mediaSource, resetPosition, resetState); updatePlaybackInfo( playbackInfo, /* positionDiscontinuity= */ false, @@ -567,10 +567,6 @@ import java.util.concurrent.CopyOnWriteArraySet; @DiscontinuityReason int positionDiscontinuityReason) { pendingOperationAcks -= operationAcks; if (pendingOperationAcks == 0) { - if (playbackInfo.timeline == null) { - // Replace internal null timeline with externally visible empty timeline. - playbackInfo = playbackInfo.copyWithTimeline(Timeline.EMPTY, playbackInfo.manifest); - } if (playbackInfo.startPositionUs == C.TIME_UNSET) { // Replace internal unset start position with externally visible start position of zero. playbackInfo = diff --git a/library/core/src/main/java/com/google/android/exoplayer2/ExoPlayerImplInternal.java b/library/core/src/main/java/com/google/android/exoplayer2/ExoPlayerImplInternal.java index e05068a7b3..24bd31c62f 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/ExoPlayerImplInternal.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/ExoPlayerImplInternal.java @@ -154,7 +154,7 @@ import java.util.Collections; seekParameters = SeekParameters.DEFAULT; playbackInfo = new PlaybackInfo( - /* timeline= */ null, /* startPositionUs= */ C.TIME_UNSET, emptyTrackSelectorResult); + Timeline.EMPTY, /* startPositionUs= */ C.TIME_UNSET, emptyTrackSelectorResult); playbackInfoUpdate = new PlaybackInfoUpdate(); rendererCapabilities = new RendererCapabilities[renderers.length]; for (int i = 0; i < renderers.length; i++) { @@ -176,8 +176,9 @@ import java.util.Collections; handler = clock.createHandler(internalPlaybackThread.getLooper(), this); } - public void prepare(MediaSource mediaSource, boolean resetPosition) { - handler.obtainMessage(MSG_PREPARE, resetPosition ? 1 : 0, 0, mediaSource) + public void prepare(MediaSource mediaSource, boolean resetPosition, boolean resetState) { + handler + .obtainMessage(MSG_PREPARE, resetPosition ? 1 : 0, resetState ? 1 : 0, mediaSource) .sendToTarget(); } @@ -286,7 +287,10 @@ import java.util.Collections; try { switch (msg.what) { case MSG_PREPARE: - prepareInternal((MediaSource) msg.obj, msg.arg1 != 0); + prepareInternal( + (MediaSource) msg.obj, + /* resetPosition= */ msg.arg1 != 0, + /* resetState= */ msg.arg2 != 0); break; case MSG_SET_PLAY_WHEN_READY: setPlayWhenReadyInternal(msg.arg1 != 0); @@ -339,7 +343,7 @@ import java.util.Collections; } maybeNotifyPlaybackInfoChanged(); } catch (ExoPlaybackException e) { - Log.e(TAG, "Renderer error.", e); + Log.e(TAG, "Playback error.", e); stopInternal(/* reset= */ false, /* acknowledgeStop= */ false); eventHandler.obtainMessage(MSG_ERROR, e).sendToTarget(); maybeNotifyPlaybackInfoChanged(); @@ -387,9 +391,9 @@ import java.util.Collections; } } - private void prepareInternal(MediaSource mediaSource, boolean resetPosition) { + private void prepareInternal(MediaSource mediaSource, boolean resetPosition, boolean resetState) { pendingPrepareCount++; - resetInternal(/* releaseMediaSource= */ true, resetPosition, /* resetState= */ true); + resetInternal(/* releaseMediaSource= */ true, resetPosition, resetState); loadControl.onPrepared(); this.mediaSource = mediaSource; setState(Player.STATE_BUFFERING); @@ -576,7 +580,6 @@ import java.util.Collections; } private void seekToInternal(SeekPosition seekPosition) throws ExoPlaybackException { - Timeline timeline = playbackInfo.timeline; playbackInfoUpdate.incrementPendingOperationAcks(/* operationAcks= */ 1); MediaPeriodId periodId; @@ -607,7 +610,7 @@ import java.util.Collections; } try { - if (mediaSource == null || timeline == null) { + if (mediaSource == null || pendingPrepareCount > 0) { // Save seek position for later, as we are still waiting for a prepared source. pendingInitialSeekPosition = seekPosition; } else if (periodPositionUs == C.TIME_UNSET) { @@ -752,7 +755,7 @@ import java.util.Collections; private int getFirstPeriodIndex() { Timeline timeline = playbackInfo.timeline; - return timeline == null || timeline.isEmpty() + return timeline.isEmpty() ? 0 : timeline.getWindow(timeline.getFirstWindowIndex(shuffleModeEnabled), window) .firstPeriodIndex; @@ -779,7 +782,7 @@ import java.util.Collections; pendingInitialSeekPosition = null; } if (resetState) { - queue.setTimeline(null); + queue.setTimeline(Timeline.EMPTY); for (PendingMessageInfo pendingMessageInfo : pendingMessages) { pendingMessageInfo.message.markAsProcessed(/* isDelivered= */ false); } @@ -788,11 +791,11 @@ import java.util.Collections; } playbackInfo = new PlaybackInfo( - resetState ? null : playbackInfo.timeline, + resetState ? Timeline.EMPTY : playbackInfo.timeline, resetState ? null : playbackInfo.manifest, resetPosition ? new MediaPeriodId(getFirstPeriodIndex()) : playbackInfo.periodId, // Set the start position to TIME_UNSET so that a subsequent seek to 0 isn't ignored. - resetPosition ? C.TIME_UNSET : playbackInfo.startPositionUs, + resetPosition ? C.TIME_UNSET : playbackInfo.positionUs, resetPosition ? C.TIME_UNSET : playbackInfo.contentPositionUs, playbackInfo.playbackState, /* isLoading= */ false, @@ -805,11 +808,11 @@ import java.util.Collections; } } - private void sendMessageInternal(PlayerMessage message) { + private void sendMessageInternal(PlayerMessage message) throws ExoPlaybackException { if (message.getPositionMs() == C.TIME_UNSET) { // If no delivery time is specified, trigger immediate message delivery. sendMessageToTarget(message); - } else if (playbackInfo.timeline == null) { + } else if (mediaSource == null || pendingPrepareCount > 0) { // Still waiting for initial timeline to resolve position. pendingMessages.add(new PendingMessageInfo(message)); } else { @@ -824,7 +827,7 @@ import java.util.Collections; } } - private void sendMessageToTarget(PlayerMessage message) { + private void sendMessageToTarget(PlayerMessage message) throws ExoPlaybackException { if (message.getHandler().getLooper() == handler.getLooper()) { deliverMessage(message); if (playbackInfo.playbackState == Player.STATE_READY @@ -838,22 +841,24 @@ import java.util.Collections; } private void sendMessageToTargetThread(final PlayerMessage message) { - message - .getHandler() - .post( - new Runnable() { - @Override - public void run() { - deliverMessage(message); - } - }); + Handler handler = message.getHandler(); + handler.post( + new Runnable() { + @Override + public void run() { + try { + deliverMessage(message); + } catch (ExoPlaybackException e) { + Log.e(TAG, "Unexpected error delivering message on external thread.", e); + throw new RuntimeException(e); + } + } + }); } - private void deliverMessage(PlayerMessage message) { + private void deliverMessage(PlayerMessage message) throws ExoPlaybackException { try { message.getTarget().handleMessage(message.getType(), message.getPayload()); - } catch (ExoPlaybackException e) { - eventHandler.obtainMessage(MSG_ERROR, e).sendToTarget(); } finally { message.markAsProcessed(/* isDelivered= */ true); } @@ -899,7 +904,8 @@ import java.util.Collections; return true; } - private void maybeTriggerPendingMessages(long oldPeriodPositionUs, long newPeriodPositionUs) { + private void maybeTriggerPendingMessages(long oldPeriodPositionUs, long newPeriodPositionUs) + throws ExoPlaybackException { if (pendingMessages.isEmpty() || playbackInfo.periodId.isAd()) { return; } @@ -1130,7 +1136,7 @@ import java.util.Collections; playbackInfo = playbackInfo.copyWithTimeline(timeline, manifest); resolvePendingMessagePositions(); - if (oldTimeline == null) { + if (pendingPrepareCount > 0) { playbackInfoUpdate.incrementPendingOperationAcks(pendingPrepareCount); pendingPrepareCount = 0; if (pendingInitialSeekPosition != null) { @@ -1292,8 +1298,8 @@ import java.util.Collections; SeekPosition seekPosition, boolean trySubsequentPeriods) { Timeline timeline = playbackInfo.timeline; Timeline seekTimeline = seekPosition.timeline; - if (timeline == null) { - // We don't have a timeline yet, so we can't resolve the position. + if (timeline.isEmpty()) { + // We don't have a valid timeline yet, so we can't resolve the position. return null; } if (seekTimeline.isEmpty()) { @@ -1349,7 +1355,7 @@ import java.util.Collections; // The player has no media source yet. return; } - if (playbackInfo.timeline == null) { + if (pendingPrepareCount > 0) { // We're waiting to get information about periods. mediaSource.maybeThrowSourceInfoRefreshError(); return; diff --git a/library/core/src/main/java/com/google/android/exoplayer2/ExoPlayerLibraryInfo.java b/library/core/src/main/java/com/google/android/exoplayer2/ExoPlayerLibraryInfo.java index 1dec506ec9..c34145a145 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/ExoPlayerLibraryInfo.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/ExoPlayerLibraryInfo.java @@ -27,27 +27,23 @@ public final class ExoPlayerLibraryInfo { */ public static final String TAG = "ExoPlayer"; - /** - * The version of the library expressed as a string, for example "1.2.3". - */ + /** The version of the library expressed as a string, for example "1.2.3". */ // Intentionally hardcoded. Do not derive from other constants (e.g. VERSION_INT) or vice versa. - public static final String VERSION = "2.7.0"; + public static final String VERSION = "2.7.1"; - /** - * The version of the library expressed as {@code "ExoPlayerLib/" + VERSION}. - */ + /** The version of the library expressed as {@code "ExoPlayerLib/" + VERSION}. */ // Intentionally hardcoded. Do not derive from other constants (e.g. VERSION) or vice versa. - public static final String VERSION_SLASHY = "ExoPlayerLib/2.7.0"; + public static final String VERSION_SLASHY = "ExoPlayerLib/2.7.1"; /** * The version of the library expressed as an integer, for example 1002003. - *

- * Three digits are used for each component of {@link #VERSION}. For example "1.2.3" has the + * + *

Three digits are used for each component of {@link #VERSION}. For example "1.2.3" has the * corresponding integer version 1002003 (001-002-003), and "123.45.6" has the corresponding * integer version 123045006 (123-045-006). */ // Intentionally hardcoded. Do not derive from other constants (e.g. VERSION) or vice versa. - public static final int VERSION_INT = 2007000; + public static final int VERSION_INT = 2007001; /** * Whether the library was compiled with {@link com.google.android.exoplayer2.util.Assertions} diff --git a/library/core/src/main/java/com/google/android/exoplayer2/PlaybackInfo.java b/library/core/src/main/java/com/google/android/exoplayer2/PlaybackInfo.java index 3ff2ec9461..3a4ee0e501 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/PlaybackInfo.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/PlaybackInfo.java @@ -24,7 +24,7 @@ import com.google.android.exoplayer2.trackselection.TrackSelectorResult; */ /* package */ final class PlaybackInfo { - public final @Nullable Timeline timeline; + public final Timeline timeline; public final @Nullable Object manifest; public final MediaPeriodId periodId; public final long startPositionUs; @@ -37,7 +37,7 @@ import com.google.android.exoplayer2.trackselection.TrackSelectorResult; public volatile long bufferedPositionUs; public PlaybackInfo( - @Nullable Timeline timeline, long startPositionUs, TrackSelectorResult trackSelectorResult) { + Timeline timeline, long startPositionUs, TrackSelectorResult trackSelectorResult) { this( timeline, /* manifest= */ null, @@ -50,7 +50,7 @@ import com.google.android.exoplayer2.trackselection.TrackSelectorResult; } public PlaybackInfo( - @Nullable Timeline timeline, + Timeline timeline, @Nullable Object manifest, MediaPeriodId periodId, long startPositionUs, diff --git a/library/core/src/main/java/com/google/android/exoplayer2/PlayerMessage.java b/library/core/src/main/java/com/google/android/exoplayer2/PlayerMessage.java index 1e8a89e102..408cbecaf1 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/PlayerMessage.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/PlayerMessage.java @@ -33,7 +33,8 @@ public final class PlayerMessage { * * @param messageType The message type. * @param payload The message payload. - * @throws ExoPlaybackException If an error occurred whilst handling the message. + * @throws ExoPlaybackException If an error occurred whilst handling the message. Should only be + * thrown by targets that handle messages on the playback thread. */ void handleMessage(int messageType, Object payload) throws ExoPlaybackException; } diff --git a/library/core/src/main/java/com/google/android/exoplayer2/audio/DefaultAudioSink.java b/library/core/src/main/java/com/google/android/exoplayer2/audio/DefaultAudioSink.java index bb9135edbf..6d12dc66e8 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/audio/DefaultAudioSink.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/audio/DefaultAudioSink.java @@ -1443,7 +1443,7 @@ public final class DefaultAudioSink implements AudioSink { rawPlaybackHeadPosition += passthroughWorkaroundPauseOffset; } - if (Util.SDK_INT <= 26) { + if (Util.SDK_INT <= 28) { if (rawPlaybackHeadPosition == 0 && lastRawPlaybackHeadPosition > 0 && state == PLAYSTATE_PLAYING) { // If connecting a Bluetooth audio device fails, the AudioTrack may be left in a state diff --git a/library/core/src/main/java/com/google/android/exoplayer2/extractor/mp4/AtomParsers.java b/library/core/src/main/java/com/google/android/exoplayer2/extractor/mp4/AtomParsers.java index 588282bc9b..37cfce7c7c 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/extractor/mp4/AtomParsers.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/extractor/mp4/AtomParsers.java @@ -49,7 +49,6 @@ import java.util.List; private static final int TYPE_sbtl = Util.getIntegerCodeForString("sbtl"); private static final int TYPE_subt = Util.getIntegerCodeForString("subt"); private static final int TYPE_clcp = Util.getIntegerCodeForString("clcp"); - private static final int TYPE_cenc = Util.getIntegerCodeForString("cenc"); private static final int TYPE_meta = Util.getIntegerCodeForString("meta"); /** @@ -128,7 +127,8 @@ import java.util.List; int sampleCount = sampleSizeBox.getSampleCount(); if (sampleCount == 0) { - return new TrackSampleTable(new long[0], new int[0], 0, new long[0], new int[0]); + return new TrackSampleTable( + new long[0], new int[0], 0, new long[0], new int[0], C.TIME_UNSET); } // Entries are byte offsets of chunks. @@ -193,6 +193,7 @@ import java.util.List; long[] timestamps; int[] flags; long timestampTimeUnits = 0; + long duration; if (!isRechunkable) { offsets = new long[sampleCount]; @@ -260,6 +261,7 @@ import java.util.List; offset += sizes[i]; remainingSamplesInChunk--; } + duration = timestampTimeUnits + timestampOffset; Assertions.checkArgument(remainingSamplesAtTimestampOffset == 0); // Remove trailing ctts entries with 0-valued sample counts. @@ -294,13 +296,15 @@ import java.util.List; maximumSize = rechunkedResults.maximumSize; timestamps = rechunkedResults.timestamps; flags = rechunkedResults.flags; + duration = rechunkedResults.duration; } + long durationUs = Util.scaleLargeTimestamp(duration, C.MICROS_PER_SECOND, track.timescale); if (track.editListDurations == null || gaplessInfoHolder.hasGaplessInfo()) { // There is no edit list, or we are ignoring it as we already have gapless metadata to apply. // This implementation does not support applying both gapless metadata and an edit list. Util.scaleLargeTimestampsInPlace(timestamps, C.MICROS_PER_SECOND, track.timescale); - return new TrackSampleTable(offsets, sizes, maximumSize, timestamps, flags); + return new TrackSampleTable(offsets, sizes, maximumSize, timestamps, flags, durationUs); } // See the BMFF spec (ISO 14496-12) subsection 8.6.6. Edit lists that require prerolling from a @@ -317,10 +321,11 @@ import java.util.List; long editStartTime = track.editListMediaTimes[0]; long editEndTime = editStartTime + Util.scaleLargeTimestamp(track.editListDurations[0], track.timescale, track.movieTimescale); - long lastSampleEndTime = timestampTimeUnits; - if (timestamps[0] <= editStartTime && editStartTime < timestamps[1] - && timestamps[timestamps.length - 1] < editEndTime && editEndTime <= lastSampleEndTime) { - long paddingTimeUnits = lastSampleEndTime - editEndTime; + if (timestamps[0] <= editStartTime + && editStartTime < timestamps[1] + && timestamps[timestamps.length - 1] < editEndTime + && editEndTime <= duration) { + long paddingTimeUnits = duration - editEndTime; long encoderDelay = Util.scaleLargeTimestamp(editStartTime - timestamps[0], track.format.sampleRate, track.timescale); long encoderPadding = Util.scaleLargeTimestamp(paddingTimeUnits, @@ -330,7 +335,7 @@ import java.util.List; gaplessInfoHolder.encoderDelay = (int) encoderDelay; gaplessInfoHolder.encoderPadding = (int) encoderPadding; Util.scaleLargeTimestampsInPlace(timestamps, C.MICROS_PER_SECOND, track.timescale); - return new TrackSampleTable(offsets, sizes, maximumSize, timestamps, flags); + return new TrackSampleTable(offsets, sizes, maximumSize, timestamps, flags, durationUs); } } } @@ -339,11 +344,15 @@ import java.util.List; // The current version of the spec leaves handling of an edit with zero segment_duration in // unfragmented files open to interpretation. We handle this as a special case and include all // samples in the edit. + long editStartTime = track.editListMediaTimes[0]; for (int i = 0; i < timestamps.length; i++) { - timestamps[i] = Util.scaleLargeTimestamp(timestamps[i] - track.editListMediaTimes[0], - C.MICROS_PER_SECOND, track.timescale); + timestamps[i] = + Util.scaleLargeTimestamp( + timestamps[i] - editStartTime, C.MICROS_PER_SECOND, track.timescale); } - return new TrackSampleTable(offsets, sizes, maximumSize, timestamps, flags); + durationUs = + Util.scaleLargeTimestamp(duration - editStartTime, C.MICROS_PER_SECOND, track.timescale); + return new TrackSampleTable(offsets, sizes, maximumSize, timestamps, flags, durationUs); } // Omit any sample at the end point of an edit for audio tracks. @@ -354,13 +363,15 @@ import java.util.List; int nextSampleIndex = 0; boolean copyMetadata = false; for (int i = 0; i < track.editListDurations.length; i++) { - long mediaTime = track.editListMediaTimes[i]; - if (mediaTime != -1) { - long duration = Util.scaleLargeTimestamp(track.editListDurations[i], track.timescale, - track.movieTimescale); - int startIndex = Util.binarySearchCeil(timestamps, mediaTime, true, true); - int endIndex = Util.binarySearchCeil(timestamps, mediaTime + duration, omitClippedSample, - false); + long editMediaTime = track.editListMediaTimes[i]; + if (editMediaTime != -1) { + long editDuration = + Util.scaleLargeTimestamp( + track.editListDurations[i], track.timescale, track.movieTimescale); + int startIndex = Util.binarySearchCeil(timestamps, editMediaTime, true, true); + int endIndex = + Util.binarySearchCeil( + timestamps, editMediaTime + editDuration, omitClippedSample, false); editedSampleCount += endIndex - startIndex; copyMetadata |= nextSampleIndex != startIndex; nextSampleIndex = endIndex; @@ -377,12 +388,13 @@ import java.util.List; long pts = 0; int sampleIndex = 0; for (int i = 0; i < track.editListDurations.length; i++) { - long mediaTime = track.editListMediaTimes[i]; - long duration = track.editListDurations[i]; - if (mediaTime != -1) { - long endMediaTime = mediaTime + Util.scaleLargeTimestamp(duration, track.timescale, - track.movieTimescale); - int startIndex = Util.binarySearchCeil(timestamps, mediaTime, true, true); + long editMediaTime = track.editListMediaTimes[i]; + long editDuration = track.editListDurations[i]; + if (editMediaTime != -1) { + long endMediaTime = + editMediaTime + + Util.scaleLargeTimestamp(editDuration, track.timescale, track.movieTimescale); + int startIndex = Util.binarySearchCeil(timestamps, editMediaTime, true, true); int endIndex = Util.binarySearchCeil(timestamps, endMediaTime, omitClippedSample, false); if (copyMetadata) { int count = endIndex - startIndex; @@ -392,8 +404,9 @@ import java.util.List; } for (int j = startIndex; j < endIndex; j++) { long ptsUs = Util.scaleLargeTimestamp(pts, C.MICROS_PER_SECOND, track.movieTimescale); - long timeInSegmentUs = Util.scaleLargeTimestamp(timestamps[j] - mediaTime, - C.MICROS_PER_SECOND, track.timescale); + long timeInSegmentUs = + Util.scaleLargeTimestamp( + timestamps[j] - editMediaTime, C.MICROS_PER_SECOND, track.timescale); editedTimestamps[sampleIndex] = ptsUs + timeInSegmentUs; if (copyMetadata && editedSizes[sampleIndex] > editedMaximumSize) { editedMaximumSize = sizes[j]; @@ -401,8 +414,9 @@ import java.util.List; sampleIndex++; } } - pts += duration; + pts += editDuration; } + long editedDurationUs = Util.scaleLargeTimestamp(pts, C.MICROS_PER_SECOND, track.timescale); boolean hasSyncSample = false; for (int i = 0; i < editedFlags.length && !hasSyncSample; i++) { @@ -413,11 +427,16 @@ import java.util.List; // Such edit lists are often (although not always) broken, so we ignore it and continue. Log.w(TAG, "Ignoring edit list: Edited sample sequence does not contain a sync sample."); Util.scaleLargeTimestampsInPlace(timestamps, C.MICROS_PER_SECOND, track.timescale); - return new TrackSampleTable(offsets, sizes, maximumSize, timestamps, flags); + return new TrackSampleTable(offsets, sizes, maximumSize, timestamps, flags, durationUs); } - return new TrackSampleTable(editedOffsets, editedSizes, editedMaximumSize, editedTimestamps, - editedFlags); + return new TrackSampleTable( + editedOffsets, + editedSizes, + editedMaximumSize, + editedTimestamps, + editedFlags, + editedDurationUs); } /** diff --git a/library/core/src/main/java/com/google/android/exoplayer2/extractor/mp4/FixedSampleSizeRechunker.java b/library/core/src/main/java/com/google/android/exoplayer2/extractor/mp4/FixedSampleSizeRechunker.java index 5dd6c6ea9f..8336a280a2 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/extractor/mp4/FixedSampleSizeRechunker.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/extractor/mp4/FixedSampleSizeRechunker.java @@ -33,13 +33,21 @@ import com.google.android.exoplayer2.util.Util; public final int maximumSize; public final long[] timestamps; public final int[] flags; + public final long duration; - private Results(long[] offsets, int[] sizes, int maximumSize, long[] timestamps, int[] flags) { + private Results( + long[] offsets, + int[] sizes, + int maximumSize, + long[] timestamps, + int[] flags, + long duration) { this.offsets = offsets; this.sizes = sizes; this.maximumSize = maximumSize; this.timestamps = timestamps; this.flags = flags; + this.duration = duration; } } @@ -95,8 +103,9 @@ import com.google.android.exoplayer2.util.Util; newSampleIndex++; } } + long duration = timestampDeltaInTimeUnits * originalSampleIndex; - return new Results(offsets, sizes, maximumSize, timestamps, flags); + return new Results(offsets, sizes, maximumSize, timestamps, flags, duration); } } diff --git a/library/core/src/main/java/com/google/android/exoplayer2/extractor/mp4/Mp4Extractor.java b/library/core/src/main/java/com/google/android/exoplayer2/extractor/mp4/Mp4Extractor.java index 112c2d1ba0..75bd2c16ee 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/extractor/mp4/Mp4Extractor.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/extractor/mp4/Mp4Extractor.java @@ -427,7 +427,10 @@ public final class Mp4Extractor implements Extractor, SeekMap { } mp4Track.trackOutput.format(format); - durationUs = Math.max(durationUs, track.durationUs); + durationUs = + Math.max( + durationUs, + track.durationUs != C.TIME_UNSET ? track.durationUs : trackSampleTable.durationUs); if (track.type == C.TRACK_TYPE_VIDEO && firstVideoTrackIndex == C.INDEX_UNSET) { firstVideoTrackIndex = tracks.size(); } diff --git a/library/core/src/main/java/com/google/android/exoplayer2/extractor/mp4/TrackSampleTable.java b/library/core/src/main/java/com/google/android/exoplayer2/extractor/mp4/TrackSampleTable.java index cf479eaf3e..9f77c49664 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/extractor/mp4/TrackSampleTable.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/extractor/mp4/TrackSampleTable.java @@ -48,9 +48,19 @@ import com.google.android.exoplayer2.util.Util; * Sample flags. */ public final int[] flags; + /** + * The duration of the track sample table in microseconds, or {@link C#TIME_UNSET} if the sample + * table is empty. + */ + public final long durationUs; - public TrackSampleTable(long[] offsets, int[] sizes, int maximumSize, long[] timestampsUs, - int[] flags) { + public TrackSampleTable( + long[] offsets, + int[] sizes, + int maximumSize, + long[] timestampsUs, + int[] flags, + long durationUs) { Assertions.checkArgument(sizes.length == timestampsUs.length); Assertions.checkArgument(offsets.length == timestampsUs.length); Assertions.checkArgument(flags.length == timestampsUs.length); @@ -60,6 +70,7 @@ import com.google.android.exoplayer2.util.Util; this.maximumSize = maximumSize; this.timestampsUs = timestampsUs; this.flags = flags; + this.durationUs = durationUs; sampleCount = offsets.length; } diff --git a/library/core/src/main/java/com/google/android/exoplayer2/video/MediaCodecVideoRenderer.java b/library/core/src/main/java/com/google/android/exoplayer2/video/MediaCodecVideoRenderer.java index 62a7657ea7..2978d00d86 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/video/MediaCodecVideoRenderer.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/video/MediaCodecVideoRenderer.java @@ -100,12 +100,12 @@ public class MediaCodecVideoRenderer extends MediaCodecRenderer { @C.VideoScalingMode private int scalingMode; private boolean renderedFirstFrame; - private boolean forceRenderFrame; private long joiningDeadlineMs; private long droppedFrameAccumulationStartTimeMs; private int droppedFrames; private int consecutiveDroppedFrameCount; private int buffersInCodecCount; + private long lastRenderTimeUs; private int pendingRotationDegrees; private float pendingPixelWidthHeightRatio; @@ -314,6 +314,7 @@ public class MediaCodecVideoRenderer extends MediaCodecRenderer { super.onStarted(); droppedFrames = 0; droppedFrameAccumulationStartTimeMs = SystemClock.elapsedRealtime(); + lastRenderTimeUs = SystemClock.elapsedRealtime() * 1000; } @Override @@ -438,7 +439,6 @@ public class MediaCodecVideoRenderer extends MediaCodecRenderer { super.releaseCodec(); } finally { buffersInCodecCount = 0; - forceRenderFrame = false; if (dummySurface != null) { if (surface == dummySurface) { surface = null; @@ -454,7 +454,6 @@ public class MediaCodecVideoRenderer extends MediaCodecRenderer { protected void flushCodec() throws ExoPlaybackException { super.flushCodec(); buffersInCodecCount = 0; - forceRenderFrame = false; } @Override @@ -546,15 +545,17 @@ public class MediaCodecVideoRenderer extends MediaCodecRenderer { if (surface == dummySurface) { // Skip frames in sync with playback, so we'll be at the right frame if the mode changes. if (isBufferLate(earlyUs)) { - forceRenderFrame = false; skipOutputBuffer(codec, bufferIndex, presentationTimeUs); return true; } return false; } - if (!renderedFirstFrame || forceRenderFrame) { - forceRenderFrame = false; + long elapsedRealtimeNowUs = SystemClock.elapsedRealtime() * 1000; + boolean isStarted = getState() == STATE_STARTED; + if (!renderedFirstFrame + || (isStarted + && shouldForceRenderOutputBuffer(earlyUs, elapsedRealtimeNowUs - lastRenderTimeUs))) { if (Util.SDK_INT >= 21) { renderOutputBufferV21(codec, bufferIndex, presentationTimeUs, System.nanoTime()); } else { @@ -563,13 +564,13 @@ public class MediaCodecVideoRenderer extends MediaCodecRenderer { return true; } - if (getState() != STATE_STARTED) { + if (!isStarted) { return false; } // Fine-grained adjustment of earlyUs based on the elapsed time since the start of the current // iteration of the rendering loop. - long elapsedSinceStartOfLoopUs = (SystemClock.elapsedRealtime() * 1000) - elapsedRealtimeUs; + long elapsedSinceStartOfLoopUs = elapsedRealtimeNowUs - elapsedRealtimeUs; earlyUs -= elapsedSinceStartOfLoopUs; // Compute the buffer's desired release time in nanoseconds. @@ -583,7 +584,6 @@ public class MediaCodecVideoRenderer extends MediaCodecRenderer { if (shouldDropBuffersToKeyframe(earlyUs, elapsedRealtimeUs) && maybeDropBuffersToKeyframe(codec, bufferIndex, presentationTimeUs, positionUs)) { - forceRenderFrame = true; return false; } else if (shouldDropOutputBuffer(earlyUs, elapsedRealtimeUs)) { dropOutputBuffer(codec, bufferIndex, presentationTimeUs); @@ -607,6 +607,7 @@ public class MediaCodecVideoRenderer extends MediaCodecRenderer { Thread.sleep((earlyUs - 10000) / 1000); } catch (InterruptedException e) { Thread.currentThread().interrupt(); + return false; } } renderOutputBuffer(codec, bufferIndex, presentationTimeUs); @@ -654,6 +655,19 @@ public class MediaCodecVideoRenderer extends MediaCodecRenderer { return isBufferVeryLate(earlyUs); } + /** + * Returns whether to force rendering an output buffer. + * + * @param earlyUs The time until the current buffer should be presented in microseconds. A + * negative value indicates that the buffer is late. + * @param elapsedSinceLastRenderUs The elapsed time since the last output buffer was rendered, in + * microseconds. + * @return Returns whether to force rendering an output buffer. + */ + protected boolean shouldForceRenderOutputBuffer(long earlyUs, long elapsedSinceLastRenderUs) { + return isBufferLate(earlyUs) && elapsedSinceLastRenderUs > 100000; + } + /** * Skips the output buffer with the specified index. * @@ -738,6 +752,7 @@ public class MediaCodecVideoRenderer extends MediaCodecRenderer { TraceUtil.beginSection("releaseOutputBuffer"); codec.releaseOutputBuffer(index, true); TraceUtil.endSection(); + lastRenderTimeUs = SystemClock.elapsedRealtime() * 1000; decoderCounters.renderedOutputBufferCount++; consecutiveDroppedFrameCount = 0; maybeNotifyRenderedFirstFrame(); @@ -753,12 +768,13 @@ public class MediaCodecVideoRenderer extends MediaCodecRenderer { * @param releaseTimeNs The wallclock time at which the frame should be displayed, in nanoseconds. */ @TargetApi(21) - protected void renderOutputBufferV21(MediaCodec codec, int index, long presentationTimeUs, - long releaseTimeNs) { + protected void renderOutputBufferV21( + MediaCodec codec, int index, long presentationTimeUs, long releaseTimeNs) { maybeNotifyVideoSizeChanged(); TraceUtil.beginSection("releaseOutputBuffer"); codec.releaseOutputBuffer(index, releaseTimeNs); TraceUtil.endSection(); + lastRenderTimeUs = SystemClock.elapsedRealtime() * 1000; decoderCounters.renderedOutputBufferCount++; consecutiveDroppedFrameCount = 0; maybeNotifyRenderedFirstFrame(); diff --git a/library/core/src/test/java/com/google/android/exoplayer2/ExoPlayerTest.java b/library/core/src/test/java/com/google/android/exoplayer2/ExoPlayerTest.java index 3d0cde5df8..b1ddcdb207 100644 --- a/library/core/src/test/java/com/google/android/exoplayer2/ExoPlayerTest.java +++ b/library/core/src/test/java/com/google/android/exoplayer2/ExoPlayerTest.java @@ -42,7 +42,9 @@ import com.google.android.exoplayer2.testutil.FakeTimeline; import com.google.android.exoplayer2.testutil.FakeTimeline.TimelineWindowDefinition; import com.google.android.exoplayer2.testutil.FakeTrackSelection; import com.google.android.exoplayer2.testutil.FakeTrackSelector; +import com.google.android.exoplayer2.testutil.RobolectricUtil; import com.google.android.exoplayer2.upstream.Allocator; +import java.io.IOException; import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; @@ -1169,10 +1171,8 @@ public final class ExoPlayerTest { Timeline timeline = new FakeTimeline(/* windowCount= */ 1); ActionSchedule actionSchedule = new ActionSchedule.Builder("testReprepareAfterPlaybackError") - .waitForPlaybackState(Player.STATE_BUFFERING) - // Cause an internal exception by seeking to an invalid position while the media source - // is still being prepared and the player doesn't immediately know it will fail. - .seek(/* windowIndex= */ 100, /* positionMs= */ 0) + .waitForPlaybackState(Player.STATE_READY) + .throwPlaybackException(ExoPlaybackException.createForSource(new IOException())) .waitForPlaybackState(Player.STATE_IDLE) .prepareSource( new FakeMediaSource(timeline, /* manifest= */ null), @@ -1203,11 +1203,8 @@ public final class ExoPlayerTest { ActionSchedule actionSchedule = new ActionSchedule.Builder("testReprepareAfterPlaybackError") .pause() - .waitForPlaybackState(Player.STATE_BUFFERING) - // Cause an internal exception by seeking to an invalid position while the media source - // is still being prepared and the player doesn't immediately know it will fail. - .seek(/* windowIndex= */ 100, /* positionMs= */ 0) - .waitForSeekProcessed() + .waitForPlaybackState(Player.STATE_READY) + .throwPlaybackException(ExoPlaybackException.createForSource(new IOException())) .waitForPlaybackState(Player.STATE_IDLE) .seek(/* positionMs= */ 50) .waitForSeekProcessed() @@ -1246,8 +1243,7 @@ public final class ExoPlayerTest { testRunner.assertTimelinesEqual(timeline, timeline); testRunner.assertTimelineChangeReasonsEqual( Player.TIMELINE_CHANGE_REASON_PREPARED, Player.TIMELINE_CHANGE_REASON_PREPARED); - testRunner.assertPositionDiscontinuityReasonsEqual( - Player.DISCONTINUITY_REASON_SEEK, Player.DISCONTINUITY_REASON_SEEK); + testRunner.assertPositionDiscontinuityReasonsEqual(Player.DISCONTINUITY_REASON_SEEK); assertThat(positionHolder[0]).isEqualTo(50); assertThat(positionHolder[1]).isEqualTo(50); } @@ -1288,6 +1284,104 @@ public final class ExoPlayerTest { testRunner.assertTimelineChangeReasonsEqual(Player.TIMELINE_CHANGE_REASON_PREPARED); } + @Test + public void testPlaybackErrorAndReprepareDoesNotResetPosition() throws Exception { + final Timeline timeline = new FakeTimeline(/* windowCount= */ 2); + final long[] positionHolder = new long[3]; + final int[] windowIndexHolder = new int[3]; + final FakeMediaSource secondMediaSource = + new FakeMediaSource(/* timeline= */ null, /* manifest= */ null); + ActionSchedule actionSchedule = + new ActionSchedule.Builder("testPlaybackErrorDoesNotResetPosition") + .pause() + .waitForPlaybackState(Player.STATE_READY) + .playUntilPosition(/* windowIndex= */ 1, /* positionMs= */ 500) + .throwPlaybackException(ExoPlaybackException.createForSource(new IOException())) + .waitForPlaybackState(Player.STATE_IDLE) + .executeRunnable( + new PlayerRunnable() { + @Override + public void run(SimpleExoPlayer player) { + // Position while in error state + positionHolder[0] = player.getCurrentPosition(); + windowIndexHolder[0] = player.getCurrentWindowIndex(); + } + }) + .prepareSource(secondMediaSource, /* resetPosition= */ false, /* resetState= */ false) + .waitForPlaybackState(Player.STATE_BUFFERING) + .executeRunnable( + new PlayerRunnable() { + @Override + public void run(SimpleExoPlayer player) { + // Position while repreparing. + positionHolder[1] = player.getCurrentPosition(); + windowIndexHolder[1] = player.getCurrentWindowIndex(); + secondMediaSource.setNewSourceInfo(timeline, /* newManifest= */ null); + } + }) + .waitForPlaybackState(Player.STATE_READY) + .executeRunnable( + new PlayerRunnable() { + @Override + public void run(SimpleExoPlayer player) { + // Position after repreparation finished. + positionHolder[2] = player.getCurrentPosition(); + windowIndexHolder[2] = player.getCurrentWindowIndex(); + } + }) + .play() + .build(); + ExoPlayerTestRunner testRunner = + new ExoPlayerTestRunner.Builder() + .setTimeline(timeline) + .setActionSchedule(actionSchedule) + .build(); + try { + testRunner.start().blockUntilActionScheduleFinished(TIMEOUT_MS).blockUntilEnded(TIMEOUT_MS); + fail(); + } catch (ExoPlaybackException e) { + // Expected exception. + } + assertThat(positionHolder[0]).isAtLeast(500L); + assertThat(positionHolder[1]).isEqualTo(positionHolder[0]); + assertThat(positionHolder[2]).isEqualTo(positionHolder[0]); + assertThat(windowIndexHolder[0]).isEqualTo(1); + assertThat(windowIndexHolder[1]).isEqualTo(1); + assertThat(windowIndexHolder[2]).isEqualTo(1); + } + + @Test + public void testPlaybackErrorTwiceStillKeepsTimeline() throws Exception { + final Timeline timeline = new FakeTimeline(/* windowCount= */ 1); + final FakeMediaSource mediaSource2 = + new FakeMediaSource(/* timeline= */ null, /* manifest= */ null); + ActionSchedule actionSchedule = + new ActionSchedule.Builder("testPlaybackErrorDoesNotResetPosition") + .pause() + .waitForPlaybackState(Player.STATE_READY) + .throwPlaybackException(ExoPlaybackException.createForSource(new IOException())) + .waitForPlaybackState(Player.STATE_IDLE) + .prepareSource(mediaSource2, /* resetPosition= */ false, /* resetState= */ false) + .waitForPlaybackState(Player.STATE_BUFFERING) + .throwPlaybackException(ExoPlaybackException.createForSource(new IOException())) + .waitForPlaybackState(Player.STATE_IDLE) + .build(); + ExoPlayerTestRunner testRunner = + new ExoPlayerTestRunner.Builder() + .setTimeline(timeline) + .setActionSchedule(actionSchedule) + .build(); + try { + testRunner.start().blockUntilActionScheduleFinished(TIMEOUT_MS).blockUntilEnded(TIMEOUT_MS); + fail(); + } catch (ExoPlaybackException e) { + // Expected exception. + } + testRunner.assertTimelinesEqual(timeline, timeline); + testRunner.assertTimelineChangeReasonsEqual( + Player.TIMELINE_CHANGE_REASON_PREPARED, Player.TIMELINE_CHANGE_REASON_PREPARED); + } + @Test public void testSendMessagesDuringPreparation() throws Exception { Timeline timeline = new FakeTimeline(/* windowCount= */ 1); @@ -1421,7 +1515,8 @@ public final class ExoPlayerTest { new FakeMediaSource(timeline, null), /* resetPosition= */ false, /* resetState= */ true) - .waitForPlaybackState(Player.STATE_READY) + .waitForPlaybackState(Player.STATE_BUFFERING) + .waitForPlaybackState(Player.STATE_ENDED) .build(); new Builder() .setTimeline(timeline) diff --git a/library/core/src/test/java/com/google/android/exoplayer2/drm/OfflineLicenseHelperTest.java b/library/core/src/test/java/com/google/android/exoplayer2/drm/OfflineLicenseHelperTest.java index f67301f017..6f62b7fcfc 100644 --- a/library/core/src/test/java/com/google/android/exoplayer2/drm/OfflineLicenseHelperTest.java +++ b/library/core/src/test/java/com/google/android/exoplayer2/drm/OfflineLicenseHelperTest.java @@ -22,8 +22,8 @@ import static org.mockito.Mockito.when; import android.util.Pair; import com.google.android.exoplayer2.C; -import com.google.android.exoplayer2.RobolectricUtil; import com.google.android.exoplayer2.drm.DrmInitData.SchemeData; +import com.google.android.exoplayer2.testutil.RobolectricUtil; import java.util.HashMap; import org.junit.After; import org.junit.Before; diff --git a/library/core/src/test/java/com/google/android/exoplayer2/source/ClippingMediaSourceTest.java b/library/core/src/test/java/com/google/android/exoplayer2/source/ClippingMediaSourceTest.java index 1e0d8681c5..a4aa3eb938 100644 --- a/library/core/src/test/java/com/google/android/exoplayer2/source/ClippingMediaSourceTest.java +++ b/library/core/src/test/java/com/google/android/exoplayer2/source/ClippingMediaSourceTest.java @@ -20,7 +20,6 @@ import static org.junit.Assert.fail; import com.google.android.exoplayer2.C; import com.google.android.exoplayer2.Player; -import com.google.android.exoplayer2.RobolectricUtil; import com.google.android.exoplayer2.Timeline; import com.google.android.exoplayer2.Timeline.Period; import com.google.android.exoplayer2.Timeline.Window; @@ -29,6 +28,7 @@ import com.google.android.exoplayer2.testutil.FakeMediaSource; import com.google.android.exoplayer2.testutil.FakeTimeline; import com.google.android.exoplayer2.testutil.FakeTimeline.TimelineWindowDefinition; import com.google.android.exoplayer2.testutil.MediaSourceTestRunner; +import com.google.android.exoplayer2.testutil.RobolectricUtil; import com.google.android.exoplayer2.testutil.TimelineAsserts; import java.io.IOException; import org.junit.Before; diff --git a/library/core/src/test/java/com/google/android/exoplayer2/source/ConcatenatingMediaSourceTest.java b/library/core/src/test/java/com/google/android/exoplayer2/source/ConcatenatingMediaSourceTest.java index ccc3ddea46..465e08b5d2 100644 --- a/library/core/src/test/java/com/google/android/exoplayer2/source/ConcatenatingMediaSourceTest.java +++ b/library/core/src/test/java/com/google/android/exoplayer2/source/ConcatenatingMediaSourceTest.java @@ -19,7 +19,6 @@ import static com.google.common.truth.Truth.assertThat; import com.google.android.exoplayer2.C; import com.google.android.exoplayer2.Player; -import com.google.android.exoplayer2.RobolectricUtil; import com.google.android.exoplayer2.Timeline; import com.google.android.exoplayer2.source.MediaSource.MediaPeriodId; import com.google.android.exoplayer2.testutil.FakeMediaSource; @@ -27,6 +26,7 @@ import com.google.android.exoplayer2.testutil.FakeShuffleOrder; import com.google.android.exoplayer2.testutil.FakeTimeline; import com.google.android.exoplayer2.testutil.FakeTimeline.TimelineWindowDefinition; import com.google.android.exoplayer2.testutil.MediaSourceTestRunner; +import com.google.android.exoplayer2.testutil.RobolectricUtil; import com.google.android.exoplayer2.testutil.TimelineAsserts; import java.io.IOException; import org.junit.Test; diff --git a/library/core/src/test/java/com/google/android/exoplayer2/source/DynamicConcatenatingMediaSourceTest.java b/library/core/src/test/java/com/google/android/exoplayer2/source/DynamicConcatenatingMediaSourceTest.java index c1537c50d3..24f1ddd5ed 100644 --- a/library/core/src/test/java/com/google/android/exoplayer2/source/DynamicConcatenatingMediaSourceTest.java +++ b/library/core/src/test/java/com/google/android/exoplayer2/source/DynamicConcatenatingMediaSourceTest.java @@ -24,7 +24,6 @@ import android.os.Handler; import android.os.HandlerThread; import com.google.android.exoplayer2.C; import com.google.android.exoplayer2.Player; -import com.google.android.exoplayer2.RobolectricUtil; import com.google.android.exoplayer2.Timeline; import com.google.android.exoplayer2.source.MediaSource.MediaPeriodId; import com.google.android.exoplayer2.testutil.FakeMediaSource; @@ -32,6 +31,7 @@ import com.google.android.exoplayer2.testutil.FakeShuffleOrder; import com.google.android.exoplayer2.testutil.FakeTimeline; import com.google.android.exoplayer2.testutil.FakeTimeline.TimelineWindowDefinition; import com.google.android.exoplayer2.testutil.MediaSourceTestRunner; +import com.google.android.exoplayer2.testutil.RobolectricUtil; import com.google.android.exoplayer2.testutil.TimelineAsserts; import java.io.IOException; import java.util.Arrays; diff --git a/library/core/src/test/java/com/google/android/exoplayer2/source/LoopingMediaSourceTest.java b/library/core/src/test/java/com/google/android/exoplayer2/source/LoopingMediaSourceTest.java index f0b4772422..6aa710aff4 100644 --- a/library/core/src/test/java/com/google/android/exoplayer2/source/LoopingMediaSourceTest.java +++ b/library/core/src/test/java/com/google/android/exoplayer2/source/LoopingMediaSourceTest.java @@ -17,12 +17,12 @@ package com.google.android.exoplayer2.source; import com.google.android.exoplayer2.C; import com.google.android.exoplayer2.Player; -import com.google.android.exoplayer2.RobolectricUtil; import com.google.android.exoplayer2.Timeline; import com.google.android.exoplayer2.testutil.FakeMediaSource; import com.google.android.exoplayer2.testutil.FakeTimeline; import com.google.android.exoplayer2.testutil.FakeTimeline.TimelineWindowDefinition; import com.google.android.exoplayer2.testutil.MediaSourceTestRunner; +import com.google.android.exoplayer2.testutil.RobolectricUtil; import com.google.android.exoplayer2.testutil.TimelineAsserts; import java.io.IOException; import org.junit.Before; diff --git a/library/core/src/test/java/com/google/android/exoplayer2/source/MergingMediaSourceTest.java b/library/core/src/test/java/com/google/android/exoplayer2/source/MergingMediaSourceTest.java index b03a76c23e..839492f196 100644 --- a/library/core/src/test/java/com/google/android/exoplayer2/source/MergingMediaSourceTest.java +++ b/library/core/src/test/java/com/google/android/exoplayer2/source/MergingMediaSourceTest.java @@ -19,13 +19,13 @@ import static com.google.common.truth.Truth.assertThat; import static org.junit.Assert.fail; import com.google.android.exoplayer2.C; -import com.google.android.exoplayer2.RobolectricUtil; import com.google.android.exoplayer2.Timeline; import com.google.android.exoplayer2.source.MergingMediaSource.IllegalMergeException; import com.google.android.exoplayer2.testutil.FakeMediaSource; import com.google.android.exoplayer2.testutil.FakeTimeline; import com.google.android.exoplayer2.testutil.FakeTimeline.TimelineWindowDefinition; import com.google.android.exoplayer2.testutil.MediaSourceTestRunner; +import com.google.android.exoplayer2.testutil.RobolectricUtil; import java.io.IOException; import org.junit.Test; import org.junit.runner.RunWith; diff --git a/library/dash/build.gradle b/library/dash/build.gradle index 99441a2849..d2692eb7d9 100644 --- a/library/dash/build.gradle +++ b/library/dash/build.gradle @@ -33,17 +33,9 @@ android { } dependencies { - compile project(modulePrefix + 'library-core') - compile 'com.android.support:support-annotations:' + supportLibraryVersion - androidTestCompile project(modulePrefix + 'testutils') - androidTestCompile 'com.google.dexmaker:dexmaker:' + dexmakerVersion - androidTestCompile 'com.google.dexmaker:dexmaker-mockito:' + dexmakerVersion - androidTestCompile 'org.mockito:mockito-core:' + mockitoVersion - testCompile project(modulePrefix + 'testutils') - testCompile 'com.google.truth:truth:' + truthVersion - testCompile 'junit:junit:' + junitVersion - testCompile 'org.mockito:mockito-core:' + mockitoVersion - testCompile 'org.robolectric:robolectric:' + robolectricVersion + implementation project(modulePrefix + 'library-core') + implementation 'com.android.support:support-annotations:' + supportLibraryVersion + testImplementation project(modulePrefix + 'testutils-robolectric') } ext { diff --git a/library/dash/src/androidTest/java/com/google/android/exoplayer2/source/dash/offline/DashDownloadTestData.java b/library/dash/src/androidTest/java/com/google/android/exoplayer2/source/dash/offline/DashDownloadTestData.java deleted file mode 100644 index 50752c8a72..0000000000 --- a/library/dash/src/androidTest/java/com/google/android/exoplayer2/source/dash/offline/DashDownloadTestData.java +++ /dev/null @@ -1,100 +0,0 @@ -/* - * Copyright (C) 2017 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.exoplayer2.source.dash.offline; - -import android.net.Uri; - -/** - * Data for DASH downloading tests. - */ -/* package */ interface DashDownloadTestData { - - Uri TEST_MPD_URI = Uri.parse("test.mpd"); - - byte[] TEST_MPD = - ("\n" - + "\n" - + " \n" - + " \n" - + " \n" - + " \n" - + " \n" - + " \n" - + " \n" - + " \n" - + " \n" - + " \n" - + " \n" - // Bounded range data - + " \n" - // Unbounded range data - + " \n" - + " \n" - + " \n" - + " \n" - + " \n" - + " \n" - + " \n" - // This segment list has a 1 second offset to make sure the progressive download order - + " \n" - + " \n" - + " \n" // 1s offset - + " \n" - + " \n" - + " \n" - + " \n" - + " \n" - + " \n" - + " \n" - + " \n" - + " \n" - + " \n" - + " \n" - + " \n" - + " \n" - + " \n" - + " \n" - + " \n" - + " \n" - + " \n" - + " \n" - + " \n" - + " \n" - + " \n" - + " \n" - + " \n" - + " \n" - + " \n" - + " \n" - + " \n" - + " \n" - + " \n" - + " \n" - + "").getBytes(); - - byte[] TEST_MPD_NO_INDEX = - ("\n" - + "\n" - + " \n" - + " \n" - + " \n" - + " \n" - + " \n" - + " \n" - + " \n" - + "").getBytes(); -} diff --git a/library/dash/src/main/java/com/google/android/exoplayer2/source/dash/DashMediaSource.java b/library/dash/src/main/java/com/google/android/exoplayer2/source/dash/DashMediaSource.java index eb9b18512c..98783ac93e 100644 --- a/library/dash/src/main/java/com/google/android/exoplayer2/source/dash/DashMediaSource.java +++ b/library/dash/src/main/java/com/google/android/exoplayer2/source/dash/DashMediaSource.java @@ -50,6 +50,7 @@ import java.io.BufferedReader; import java.io.IOException; import java.io.InputStream; import java.io.InputStreamReader; +import java.nio.charset.Charset; import java.text.ParseException; import java.text.SimpleDateFormat; import java.util.Locale; @@ -1113,7 +1114,9 @@ public final class DashMediaSource implements MediaSource { @Override public Long parse(Uri uri, InputStream inputStream) throws IOException { - String firstLine = new BufferedReader(new InputStreamReader(inputStream)).readLine(); + String firstLine = + new BufferedReader(new InputStreamReader(inputStream, Charset.forName(C.UTF8_NAME))) + .readLine(); try { Matcher matcher = TIMESTAMP_WITH_TIMEZONE_PATTERN.matcher(firstLine); if (!matcher.matches()) { diff --git a/library/dash/src/androidTest/AndroidManifest.xml b/library/dash/src/test/AndroidManifest.xml similarity index 71% rename from library/dash/src/androidTest/AndroidManifest.xml rename to library/dash/src/test/AndroidManifest.xml index 39596a8165..eecf596b92 100644 --- a/library/dash/src/androidTest/AndroidManifest.xml +++ b/library/dash/src/test/AndroidManifest.xml @@ -18,16 +18,6 @@ xmlns:tools="http://schemas.android.com/tools" package="com.google.android.exoplayer2.source.dash.test"> - - - - - - - + diff --git a/library/dash/src/androidTest/assets/sample_mpd_1 b/library/dash/src/test/assets/sample_mpd_1 similarity index 100% rename from library/dash/src/androidTest/assets/sample_mpd_1 rename to library/dash/src/test/assets/sample_mpd_1 diff --git a/library/dash/src/androidTest/assets/sample_mpd_2_unknown_mime_type b/library/dash/src/test/assets/sample_mpd_2_unknown_mime_type similarity index 100% rename from library/dash/src/androidTest/assets/sample_mpd_2_unknown_mime_type rename to library/dash/src/test/assets/sample_mpd_2_unknown_mime_type diff --git a/library/dash/src/androidTest/assets/sample_mpd_3_segment_template b/library/dash/src/test/assets/sample_mpd_3_segment_template similarity index 100% rename from library/dash/src/androidTest/assets/sample_mpd_3_segment_template rename to library/dash/src/test/assets/sample_mpd_3_segment_template diff --git a/library/dash/src/androidTest/assets/sample_mpd_4_event_stream b/library/dash/src/test/assets/sample_mpd_4_event_stream similarity index 100% rename from library/dash/src/androidTest/assets/sample_mpd_4_event_stream rename to library/dash/src/test/assets/sample_mpd_4_event_stream diff --git a/library/dash/src/test/java/com/google/android/exoplayer2/source/dash/DashMediaSourceTest.java b/library/dash/src/test/java/com/google/android/exoplayer2/source/dash/DashMediaSourceTest.java new file mode 100644 index 0000000000..1c440c70be --- /dev/null +++ b/library/dash/src/test/java/com/google/android/exoplayer2/source/dash/DashMediaSourceTest.java @@ -0,0 +1,76 @@ +/* + * Copyright (C) 2017 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.exoplayer2.source.dash; + +import static com.google.common.truth.Truth.assertThat; +import static org.junit.Assert.fail; + +import com.google.android.exoplayer2.ParserException; +import com.google.android.exoplayer2.upstream.ParsingLoadable; +import com.google.android.exoplayer2.util.Util; +import java.io.ByteArrayInputStream; +import java.io.IOException; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.robolectric.RobolectricTestRunner; + +/** Unit test for {@link DashMediaSource}. */ +@RunWith(RobolectricTestRunner.class) +public final class DashMediaSourceTest { + + @Test + public void testIso8601ParserParse() throws IOException { + DashMediaSource.Iso8601Parser parser = new DashMediaSource.Iso8601Parser(); + // UTC. + assertParseStringToLong(1512381697000L, parser, "2017-12-04T10:01:37Z"); + assertParseStringToLong(1512381697000L, parser, "2017-12-04T10:01:37+00:00"); + assertParseStringToLong(1512381697000L, parser, "2017-12-04T10:01:37+0000"); + assertParseStringToLong(1512381697000L, parser, "2017-12-04T10:01:37+00"); + // Positive timezone offsets. + assertParseStringToLong(1512381697000L - 4980000L, parser, "2017-12-04T10:01:37+01:23"); + assertParseStringToLong(1512381697000L - 4980000L, parser, "2017-12-04T10:01:37+0123"); + assertParseStringToLong(1512381697000L - 3600000L, parser, "2017-12-04T10:01:37+01"); + // Negative timezone offsets with minus character. + assertParseStringToLong(1512381697000L + 4980000L, parser, "2017-12-04T10:01:37-01:23"); + assertParseStringToLong(1512381697000L + 4980000L, parser, "2017-12-04T10:01:37-0123"); + assertParseStringToLong(1512381697000L + 3600000L, parser, "2017-12-04T10:01:37-01:00"); + assertParseStringToLong(1512381697000L + 3600000L, parser, "2017-12-04T10:01:37-0100"); + assertParseStringToLong(1512381697000L + 3600000L, parser, "2017-12-04T10:01:37-01"); + // Negative timezone offsets with hyphen character. + assertParseStringToLong(1512381697000L + 4980000L, parser, "2017-12-04T10:01:37−01:23"); + assertParseStringToLong(1512381697000L + 4980000L, parser, "2017-12-04T10:01:37−0123"); + assertParseStringToLong(1512381697000L + 3600000L, parser, "2017-12-04T10:01:37−01:00"); + assertParseStringToLong(1512381697000L + 3600000L, parser, "2017-12-04T10:01:37−0100"); + assertParseStringToLong(1512381697000L + 3600000L, parser, "2017-12-04T10:01:37−01"); + } + + @Test + public void testIso8601ParserParseMissingTimezone() throws IOException { + DashMediaSource.Iso8601Parser parser = new DashMediaSource.Iso8601Parser(); + try { + assertParseStringToLong(0, parser, "2017-12-04T10:01:37"); + fail(); + } catch (ParserException e) { + // Expected. + } + } + + private static void assertParseStringToLong( + long expected, ParsingLoadable.Parser parser, String data) throws IOException { + long actual = parser.parse(null, new ByteArrayInputStream(Util.getUtf8Bytes(data))); + assertThat(actual).isEqualTo(expected); + } +} diff --git a/library/dash/src/androidTest/java/com/google/android/exoplayer2/source/dash/DashUtilTest.java b/library/dash/src/test/java/com/google/android/exoplayer2/source/dash/DashUtilTest.java similarity index 81% rename from library/dash/src/androidTest/java/com/google/android/exoplayer2/source/dash/DashUtilTest.java rename to library/dash/src/test/java/com/google/android/exoplayer2/source/dash/DashUtilTest.java index 4ddbf429ab..15fa3b3355 100644 --- a/library/dash/src/androidTest/java/com/google/android/exoplayer2/source/dash/DashUtilTest.java +++ b/library/dash/src/test/java/com/google/android/exoplayer2/source/dash/DashUtilTest.java @@ -28,33 +28,38 @@ import com.google.android.exoplayer2.source.dash.manifest.SegmentBase.SingleSegm import com.google.android.exoplayer2.upstream.DummyDataSource; import com.google.android.exoplayer2.util.MimeTypes; import java.util.Arrays; -import junit.framework.TestCase; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.robolectric.RobolectricTestRunner; -/** - * Unit tests for {@link DashUtil}. - */ -public final class DashUtilTest extends TestCase { +/** Unit tests for {@link DashUtil}. */ +@RunWith(RobolectricTestRunner.class) +public final class DashUtilTest { + @Test public void testLoadDrmInitDataFromManifest() throws Exception { Period period = newPeriod(newAdaptationSets(newRepresentations(newDrmInitData()))); DrmInitData drmInitData = DashUtil.loadDrmInitData(DummyDataSource.INSTANCE, period); assertThat(drmInitData).isEqualTo(newDrmInitData()); } + @Test public void testLoadDrmInitDataMissing() throws Exception { Period period = newPeriod(newAdaptationSets(newRepresentations(null /* no init data */))); DrmInitData drmInitData = DashUtil.loadDrmInitData(DummyDataSource.INSTANCE, period); assertThat(drmInitData).isNull(); } + @Test public void testLoadDrmInitDataNoRepresentations() throws Exception { - Period period = newPeriod(newAdaptationSets(/* no representation */)); + Period period = newPeriod(newAdaptationSets(/* no representation */ )); DrmInitData drmInitData = DashUtil.loadDrmInitData(DummyDataSource.INSTANCE, period); assertThat(drmInitData).isNull(); } + @Test public void testLoadDrmInitDataNoAdaptationSets() throws Exception { - Period period = newPeriod(/* no adaptation set */); + Period period = newPeriod(/* no adaptation set */ ); DrmInitData drmInitData = DashUtil.loadDrmInitData(DummyDataSource.INSTANCE, period); assertThat(drmInitData).isNull(); } @@ -68,8 +73,18 @@ public final class DashUtilTest extends TestCase { } private static Representation newRepresentations(DrmInitData drmInitData) { - Format format = Format.createVideoContainerFormat("id", MimeTypes.VIDEO_MP4, - MimeTypes.VIDEO_H264, "", Format.NO_VALUE, 1024, 768, Format.NO_VALUE, null, 0); + Format format = + Format.createVideoContainerFormat( + "id", + MimeTypes.VIDEO_MP4, + MimeTypes.VIDEO_H264, + "", + Format.NO_VALUE, + 1024, + 768, + Format.NO_VALUE, + null, + 0); if (drmInitData != null) { format = format.copyWithDrmInitData(drmInitData); } @@ -77,8 +92,7 @@ public final class DashUtilTest extends TestCase { } private static DrmInitData newDrmInitData() { - return new DrmInitData(new SchemeData(C.WIDEVINE_UUID, "mimeType", - new byte[] {1, 4, 7, 0, 3, 6})); + return new DrmInitData( + new SchemeData(C.WIDEVINE_UUID, "mimeType", new byte[] {1, 4, 7, 0, 3, 6})); } - } diff --git a/library/dash/src/test/java/com/google/android/exoplayer2/source/dash/EventSampleStreamTest.java b/library/dash/src/test/java/com/google/android/exoplayer2/source/dash/EventSampleStreamTest.java new file mode 100644 index 0000000000..9c3752551a --- /dev/null +++ b/library/dash/src/test/java/com/google/android/exoplayer2/source/dash/EventSampleStreamTest.java @@ -0,0 +1,356 @@ +/* + * Copyright (C) 2017 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.exoplayer2.source.dash; + +import static com.google.common.truth.Truth.assertThat; + +import com.google.android.exoplayer2.C; +import com.google.android.exoplayer2.Format; +import com.google.android.exoplayer2.FormatHolder; +import com.google.android.exoplayer2.decoder.DecoderInputBuffer; +import com.google.android.exoplayer2.metadata.MetadataInputBuffer; +import com.google.android.exoplayer2.metadata.emsg.EventMessage; +import com.google.android.exoplayer2.metadata.emsg.EventMessageEncoder; +import com.google.android.exoplayer2.source.dash.manifest.EventStream; +import com.google.android.exoplayer2.util.MimeTypes; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.robolectric.RobolectricTestRunner; + +/** + * Unit test for {@link EventSampleStream}. + */ +@RunWith(RobolectricTestRunner.class) +public final class EventSampleStreamTest { + + private static final String SCHEME_ID = "urn:test"; + private static final String VALUE = "123"; + private static final Format FORMAT = Format.createSampleFormat("urn:test/123", + MimeTypes.APPLICATION_EMSG, null, Format.NO_VALUE, null); + private static final byte[] MESSAGE_DATA = new byte[] {1, 2, 3, 4}; + private static final long DURATION_MS = 3000; + private static final long TIME_SCALE = 1000; + + private FormatHolder formatHolder; + private MetadataInputBuffer inputBuffer; + private EventMessageEncoder eventMessageEncoder; + + @Before + public void setUp() { + formatHolder = new FormatHolder(); + inputBuffer = new MetadataInputBuffer(); + eventMessageEncoder = new EventMessageEncoder(); + } + + /** + * Tests that {@link EventSampleStream#readData(FormatHolder, DecoderInputBuffer, boolean)} will + * return format for the first call. + */ + @Test + public void testReadDataReturnFormatForFirstRead() { + EventStream eventStream = new EventStream(SCHEME_ID, VALUE, TIME_SCALE, + new long[0], new EventMessage[0]); + EventSampleStream sampleStream = new EventSampleStream(eventStream, FORMAT, false); + + int result = readData(sampleStream); + assertThat(result).isEqualTo(C.RESULT_FORMAT_READ); + assertThat(formatHolder.format).isEqualTo(FORMAT); + } + + /** + * Tests that a non-dynamic {@link EventSampleStream} will return a buffer with + * {@link C#BUFFER_FLAG_END_OF_STREAM} when trying to read sample out-of-bound. + */ + @Test + public void testReadDataOutOfBoundReturnEndOfStreamAfterFormatForNonDynamicEventSampleStream() { + EventStream eventStream = new EventStream(SCHEME_ID, VALUE, TIME_SCALE, + new long[0], new EventMessage[0]); + EventSampleStream sampleStream = new EventSampleStream(eventStream, FORMAT, false); + // first read - read format + readData(sampleStream); + + int result = readData(sampleStream); + assertThat(result).isEqualTo(C.RESULT_BUFFER_READ); + assertThat(inputBuffer.isEndOfStream()).isTrue(); + } + + /** + * Tests that a dynamic {@link EventSampleStream} will return {@link C#RESULT_NOTHING_READ} + * when trying to read sample out-of-bound. + */ + @Test + public void testReadDataOutOfBoundReturnEndOfStreamAfterFormatForDynamicEventSampleStream() { + EventStream eventStream = new EventStream(SCHEME_ID, VALUE, TIME_SCALE, + new long[0], new EventMessage[0]); + EventSampleStream sampleStream = new EventSampleStream(eventStream, FORMAT, true); + // first read - read format + readData(sampleStream); + + int result = readData(sampleStream); + assertThat(result).isEqualTo(C.RESULT_NOTHING_READ); + } + + /** + * Tests that {@link EventSampleStream#readData(FormatHolder, DecoderInputBuffer, boolean)} will + * return sample data after the first call. + */ + @Test + public void testReadDataReturnDataAfterFormat() { + long presentationTimeUs = 1000000; + EventMessage eventMessage = newEventMessageWithIdAndTime(1, presentationTimeUs); + EventStream eventStream = new EventStream(SCHEME_ID, VALUE, TIME_SCALE, + new long[] {presentationTimeUs}, new EventMessage[] {eventMessage}); + EventSampleStream sampleStream = new EventSampleStream(eventStream, FORMAT, false); + // first read - read format + readData(sampleStream); + + int result = readData(sampleStream); + assertThat(result).isEqualTo(C.RESULT_BUFFER_READ); + assertThat(inputBuffer.data.array()) + .isEqualTo(getEncodedMessage(eventMessage)); + } + + /** + * Tests that {@link EventSampleStream#skipData(long)} will skip until the given position, and + * the next {@link EventSampleStream#readData(FormatHolder, DecoderInputBuffer, boolean)} call + * will return sample data from that position. + */ + @Test + public void testSkipDataThenReadDataReturnDataFromSkippedPosition() { + long presentationTimeUs1 = 1000000; + long presentationTimeUs2 = 2000000; + EventMessage eventMessage1 = newEventMessageWithIdAndTime(1, presentationTimeUs1); + EventMessage eventMessage2 = newEventMessageWithIdAndTime(2, presentationTimeUs2); + EventStream eventStream = new EventStream(SCHEME_ID, VALUE, TIME_SCALE, + new long[] {presentationTimeUs1, presentationTimeUs2}, + new EventMessage[] {eventMessage1, eventMessage2}); + EventSampleStream sampleStream = new EventSampleStream(eventStream, FORMAT, false); + // first read - read format + readData(sampleStream); + + int skipped = sampleStream.skipData(presentationTimeUs2); + int result = readData(sampleStream); + assertThat(skipped).isEqualTo(1); + assertThat(result).isEqualTo(C.RESULT_BUFFER_READ); + assertThat(inputBuffer.data.array()) + .isEqualTo(getEncodedMessage(eventMessage2)); + } + + /** + * Tests that {@link EventSampleStream#seekToUs(long)} (long)} will seek to the given position, + * and the next {@link EventSampleStream#readData(FormatHolder, DecoderInputBuffer, boolean)} call + * will return sample data from that position. + */ + @Test + public void testSeekToUsThenReadDataReturnDataFromSeekPosition() { + long presentationTimeUs1 = 1000000; + long presentationTimeUs2 = 2000000; + EventMessage eventMessage1 = newEventMessageWithIdAndTime(1, presentationTimeUs1); + EventMessage eventMessage2 = newEventMessageWithIdAndTime(2, presentationTimeUs2); + EventStream eventStream = new EventStream(SCHEME_ID, VALUE, TIME_SCALE, + new long[] {presentationTimeUs1, presentationTimeUs2}, + new EventMessage[] {eventMessage1, eventMessage2}); + EventSampleStream sampleStream = new EventSampleStream(eventStream, FORMAT, false); + // first read - read format + readData(sampleStream); + + sampleStream.seekToUs(presentationTimeUs2); + int result = readData(sampleStream); + assertThat(result).isEqualTo(C.RESULT_BUFFER_READ); + assertThat(inputBuffer.data.array()) + .isEqualTo(getEncodedMessage(eventMessage2)); + } + + /** + * Tests that {@link EventSampleStream#updateEventStream(EventStream, boolean)} will update the + * underlying event stream, but keep the read timestamp, so the next + * {@link EventSampleStream#readData(FormatHolder, DecoderInputBuffer, boolean)} call + * will return sample data from after the last read sample timestamp. + */ + @Test + public void testUpdateEventStreamContinueToReadAfterLastReadSamplePresentationTime() { + long presentationTimeUs1 = 1000000; + long presentationTimeUs2 = 2000000; + long presentationTimeUs3 = 3000000; + EventMessage eventMessage1 = newEventMessageWithIdAndTime(1, presentationTimeUs1); + EventMessage eventMessage2 = newEventMessageWithIdAndTime(2, presentationTimeUs2); + EventMessage eventMessage3 = newEventMessageWithIdAndTime(3, presentationTimeUs3); + EventStream eventStream1 = new EventStream(SCHEME_ID, VALUE, TIME_SCALE, + new long[] {presentationTimeUs1, presentationTimeUs2}, + new EventMessage[] {eventMessage1, eventMessage2}); + EventStream eventStream2 = new EventStream(SCHEME_ID, VALUE, TIME_SCALE, + new long[] {presentationTimeUs1, presentationTimeUs2, presentationTimeUs3}, + new EventMessage[] {eventMessage1, eventMessage2, eventMessage3}); + EventSampleStream sampleStream = new EventSampleStream(eventStream1, FORMAT, true); + // first read - read format + readData(sampleStream); + // read first and second sample. + readData(sampleStream); + readData(sampleStream); + + sampleStream.updateEventStream(eventStream2, true); + int result = readData(sampleStream); + assertThat(result).isEqualTo(C.RESULT_BUFFER_READ); + assertThat(inputBuffer.data.array()) + .isEqualTo(getEncodedMessage(eventMessage3)); + } + + /** + * Tests that {@link EventSampleStream#updateEventStream(EventStream, boolean)} will update the + * underlying event stream, but keep the timestamp the stream has skipped to, so the next + * {@link EventSampleStream#readData(FormatHolder, DecoderInputBuffer, boolean)} call + * will return sample data from the skipped position. + */ + @Test + public void testSkipDataThenUpdateStreamContinueToReadFromSkippedPosition() { + long presentationTimeUs1 = 1000000; + long presentationTimeUs2 = 2000000; + long presentationTimeUs3 = 3000000; + EventMessage eventMessage1 = newEventMessageWithIdAndTime(1, presentationTimeUs1); + EventMessage eventMessage2 = newEventMessageWithIdAndTime(2, presentationTimeUs2); + EventMessage eventMessage3 = newEventMessageWithIdAndTime(3, presentationTimeUs3); + EventStream eventStream1 = new EventStream(SCHEME_ID, VALUE, TIME_SCALE, + new long[] {presentationTimeUs1, presentationTimeUs2}, + new EventMessage[] {eventMessage1, eventMessage2}); + EventStream eventStream2 = new EventStream(SCHEME_ID, VALUE, TIME_SCALE, + new long[] {presentationTimeUs1, presentationTimeUs2, presentationTimeUs3}, + new EventMessage[] {eventMessage1, eventMessage2, eventMessage3}); + EventSampleStream sampleStream = new EventSampleStream(eventStream1, FORMAT, true); + // first read - read format + readData(sampleStream); + sampleStream.skipData(presentationTimeUs2 + 1); + + sampleStream.updateEventStream(eventStream2, true); + int result = readData(sampleStream); + assertThat(result).isEqualTo(C.RESULT_BUFFER_READ); + assertThat(inputBuffer.data.array()) + .isEqualTo(getEncodedMessage(eventMessage3)); + } + + /** + * Tests that {@link EventSampleStream#skipData(long)} will only skip to the point right after + * it last event. A following {@link EventSampleStream#updateEventStream(EventStream, boolean)} + * will update the underlying event stream and keep the timestamp the stream has skipped to, so + * the next {@link EventSampleStream#readData(FormatHolder, DecoderInputBuffer, boolean)} call + * will return sample data from the skipped position. + */ + @Test + public void testSkipDataThenUpdateStreamContinueToReadDoNotSkippedMoreThanAvailable() { + long presentationTimeUs1 = 1000000; + long presentationTimeUs2 = 2000000; + long presentationTimeUs3 = 3000000; + EventMessage eventMessage1 = newEventMessageWithIdAndTime(1, presentationTimeUs1); + EventMessage eventMessage2 = newEventMessageWithIdAndTime(2, presentationTimeUs2); + EventMessage eventMessage3 = newEventMessageWithIdAndTime(3, presentationTimeUs3); + EventStream eventStream1 = new EventStream(SCHEME_ID, VALUE, TIME_SCALE, + new long[] {presentationTimeUs1}, + new EventMessage[] {eventMessage1}); + EventStream eventStream2 = new EventStream(SCHEME_ID, VALUE, TIME_SCALE, + new long[] {presentationTimeUs1, presentationTimeUs2, presentationTimeUs3}, + new EventMessage[] {eventMessage1, eventMessage2, eventMessage3}); + EventSampleStream sampleStream = new EventSampleStream(eventStream1, FORMAT, true); + // first read - read format + readData(sampleStream); + // even though the skip call is to 2000001, since eventStream1 only contains sample until + // 1000000, it will only skip to 1000001. + sampleStream.skipData(presentationTimeUs2 + 1); + + sampleStream.updateEventStream(eventStream2, true); + int result = readData(sampleStream); + assertThat(result).isEqualTo(C.RESULT_BUFFER_READ); + assertThat(inputBuffer.data.array()) + .isEqualTo(getEncodedMessage(eventMessage2)); + } + + /** + * Tests that {@link EventSampleStream#updateEventStream(EventStream, boolean)} will update the + * underlying event stream, but keep the timestamp the stream has seek to, so the next + * {@link EventSampleStream#readData(FormatHolder, DecoderInputBuffer, boolean)} call + * will return sample data from the seek position. + */ + @Test + public void testSeekToUsThenUpdateStreamContinueToReadFromSeekPosition() { + long presentationTimeUs1 = 1000000; + long presentationTimeUs2 = 2000000; + long presentationTimeUs3 = 3000000; + EventMessage eventMessage1 = newEventMessageWithIdAndTime(1, presentationTimeUs1); + EventMessage eventMessage2 = newEventMessageWithIdAndTime(2, presentationTimeUs2); + EventMessage eventMessage3 = newEventMessageWithIdAndTime(3, presentationTimeUs3); + EventStream eventStream1 = new EventStream(SCHEME_ID, VALUE, TIME_SCALE, + new long[] {presentationTimeUs1, presentationTimeUs2}, + new EventMessage[] {eventMessage1, eventMessage2}); + EventStream eventStream2 = new EventStream(SCHEME_ID, VALUE, TIME_SCALE, + new long[] {presentationTimeUs1, presentationTimeUs2, presentationTimeUs3}, + new EventMessage[] {eventMessage1, eventMessage2, eventMessage3}); + EventSampleStream sampleStream = new EventSampleStream(eventStream1, FORMAT, true); + // first read - read format + readData(sampleStream); + sampleStream.seekToUs(presentationTimeUs2); + + sampleStream.updateEventStream(eventStream2, true); + int result = readData(sampleStream); + assertThat(result).isEqualTo(C.RESULT_BUFFER_READ); + assertThat(inputBuffer.data.array()) + .isEqualTo(getEncodedMessage(eventMessage2)); + } + + /** + * Tests that {@link EventSampleStream#updateEventStream(EventStream, boolean)} will update the + * underlying event stream, but keep the timestamp the stream has seek to, so the next + * {@link EventSampleStream#readData(FormatHolder, DecoderInputBuffer, boolean)} call + * will return sample data from the seek position. + */ + @Test + public void testSeekToThenUpdateStreamContinueToReadFromSeekPositionEvenSeekMoreThanAvailable() { + long presentationTimeUs1 = 1000000; + long presentationTimeUs2 = 2000000; + long presentationTimeUs3 = 3000000; + EventMessage eventMessage1 = newEventMessageWithIdAndTime(1, presentationTimeUs1); + EventMessage eventMessage2 = newEventMessageWithIdAndTime(2, presentationTimeUs2); + EventMessage eventMessage3 = newEventMessageWithIdAndTime(3, presentationTimeUs3); + EventStream eventStream1 = new EventStream(SCHEME_ID, VALUE, TIME_SCALE, + new long[] {presentationTimeUs1}, + new EventMessage[] {eventMessage1}); + EventStream eventStream2 = new EventStream(SCHEME_ID, VALUE, TIME_SCALE, + new long[] {presentationTimeUs1, presentationTimeUs2, presentationTimeUs3}, + new EventMessage[] {eventMessage1, eventMessage2, eventMessage3}); + EventSampleStream sampleStream = new EventSampleStream(eventStream1, FORMAT, true); + // first read - read format + readData(sampleStream); + sampleStream.seekToUs(presentationTimeUs2 + 1); + + sampleStream.updateEventStream(eventStream2, true); + int result = readData(sampleStream); + assertThat(result).isEqualTo(C.RESULT_BUFFER_READ); + assertThat(inputBuffer.data.array()) + .isEqualTo(getEncodedMessage(eventMessage3)); + } + + private int readData(EventSampleStream sampleStream) { + inputBuffer.clear(); + return sampleStream.readData(formatHolder, inputBuffer, false); + } + + private EventMessage newEventMessageWithIdAndTime(int id, long presentationTimeUs) { + return new EventMessage(SCHEME_ID, VALUE, DURATION_MS, id, MESSAGE_DATA, presentationTimeUs); + } + + private byte[] getEncodedMessage(EventMessage eventMessage) { + return eventMessageEncoder.encode(eventMessage, TIME_SCALE); + } + +} diff --git a/library/dash/src/androidTest/java/com/google/android/exoplayer2/source/dash/manifest/DashManifestParserTest.java b/library/dash/src/test/java/com/google/android/exoplayer2/source/dash/manifest/DashManifestParserTest.java similarity index 85% rename from library/dash/src/androidTest/java/com/google/android/exoplayer2/source/dash/manifest/DashManifestParserTest.java rename to library/dash/src/test/java/com/google/android/exoplayer2/source/dash/manifest/DashManifestParserTest.java index 3b7982592d..6f14c8790f 100644 --- a/library/dash/src/androidTest/java/com/google/android/exoplayer2/source/dash/manifest/DashManifestParserTest.java +++ b/library/dash/src/test/java/com/google/android/exoplayer2/source/dash/manifest/DashManifestParserTest.java @@ -18,39 +18,47 @@ package com.google.android.exoplayer2.source.dash.manifest; import static com.google.common.truth.Truth.assertThat; import android.net.Uri; -import android.test.InstrumentationTestCase; +import com.google.android.exoplayer2.C; import com.google.android.exoplayer2.Format; import com.google.android.exoplayer2.metadata.emsg.EventMessage; import com.google.android.exoplayer2.testutil.TestUtil; import java.io.IOException; +import java.nio.charset.Charset; import java.util.Collections; import java.util.List; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.robolectric.RobolectricTestRunner; +import org.robolectric.RuntimeEnvironment; -/** - * Unit tests for {@link DashManifestParser}. - */ -public class DashManifestParserTest extends InstrumentationTestCase { +/** Unit tests for {@link DashManifestParser}. */ +@RunWith(RobolectricTestRunner.class) +public class DashManifestParserTest { private static final String SAMPLE_MPD_1 = "sample_mpd_1"; private static final String SAMPLE_MPD_2_UNKNOWN_MIME_TYPE = "sample_mpd_2_unknown_mime_type"; private static final String SAMPLE_MPD_3_SEGMENT_TEMPLATE = "sample_mpd_3_segment_template"; private static final String SAMPLE_MPD_4_EVENT_STREAM = "sample_mpd_4_event_stream"; - /** - * Simple test to ensure the sample manifests parse without any exceptions being thrown. - */ + /** Simple test to ensure the sample manifests parse without any exceptions being thrown. */ + @Test public void testParseMediaPresentationDescription() throws IOException { DashManifestParser parser = new DashManifestParser(); - parser.parse(Uri.parse("https://example.com/test.mpd"), - TestUtil.getInputStream(getInstrumentation(), SAMPLE_MPD_1)); - parser.parse(Uri.parse("https://example.com/test.mpd"), - TestUtil.getInputStream(getInstrumentation(), SAMPLE_MPD_2_UNKNOWN_MIME_TYPE)); + parser.parse( + Uri.parse("https://example.com/test.mpd"), + TestUtil.getInputStream(RuntimeEnvironment.application, SAMPLE_MPD_1)); + parser.parse( + Uri.parse("https://example.com/test.mpd"), + TestUtil.getInputStream(RuntimeEnvironment.application, SAMPLE_MPD_2_UNKNOWN_MIME_TYPE)); } + @Test public void testParseMediaPresentationDescriptionWithSegmentTemplate() throws IOException { DashManifestParser parser = new DashManifestParser(); - DashManifest mpd = parser.parse(Uri.parse("https://example.com/test.mpd"), - TestUtil.getInputStream(getInstrumentation(), SAMPLE_MPD_3_SEGMENT_TEMPLATE)); + DashManifest mpd = + parser.parse( + Uri.parse("https://example.com/test.mpd"), + TestUtil.getInputStream(RuntimeEnvironment.application, SAMPLE_MPD_3_SEGMENT_TEMPLATE)); assertThat(mpd.getPeriodCount()).isEqualTo(1); @@ -75,11 +83,13 @@ public class DashManifestParserTest extends InstrumentationTestCase { } } - public void testParseMediaPresentationDescriptionCanParseEventStream() - throws IOException { + @Test + public void testParseMediaPresentationDescriptionCanParseEventStream() throws IOException { DashManifestParser parser = new DashManifestParser(); - DashManifest mpd = parser.parse(Uri.parse("https://example.com/test.mpd"), - TestUtil.getInputStream(getInstrumentation(), SAMPLE_MPD_4_EVENT_STREAM)); + DashManifest mpd = + parser.parse( + Uri.parse("https://example.com/test.mpd"), + TestUtil.getInputStream(RuntimeEnvironment.application, SAMPLE_MPD_4_EVENT_STREAM)); Period period = mpd.getPeriod(0); assertThat(period.eventStreams).hasSize(3); @@ -87,8 +97,14 @@ public class DashManifestParserTest extends InstrumentationTestCase { // assert text-only event stream EventStream eventStream1 = period.eventStreams.get(0); assertThat(eventStream1.events.length).isEqualTo(1); - EventMessage expectedEvent1 = new EventMessage("urn:uuid:XYZY", "call", 10000, 0, - "+ 1 800 10101010".getBytes(), 0); + EventMessage expectedEvent1 = + new EventMessage( + "urn:uuid:XYZY", + "call", + 10000, + 0, + "+ 1 800 10101010".getBytes(Charset.forName(C.UTF8_NAME)), + 0); assertThat(eventStream1.events[0]).isEqualTo(expectedEvent1); // assert CData-structured event stream @@ -135,6 +151,7 @@ public class DashManifestParserTest extends InstrumentationTestCase { 1000000000)); } + @Test public void testParseCea608AccessibilityChannel() { assertThat( DashManifestParser.parseCea608AccessibilityChannel( @@ -175,6 +192,7 @@ public class DashManifestParserTest extends InstrumentationTestCase { .isEqualTo(Format.NO_VALUE); } + @Test public void testParseCea708AccessibilityChannel() { assertThat( DashManifestParser.parseCea708AccessibilityChannel( @@ -226,5 +244,4 @@ public class DashManifestParserTest extends InstrumentationTestCase { private static List buildCea708AccessibilityDescriptors(String value) { return Collections.singletonList(new Descriptor("urn:scte:dash:cc:cea-708:2015", value, null)); } - } diff --git a/library/dash/src/androidTest/java/com/google/android/exoplayer2/source/dash/manifest/DashManifestTest.java b/library/dash/src/test/java/com/google/android/exoplayer2/source/dash/manifest/DashManifestTest.java similarity index 54% rename from library/dash/src/androidTest/java/com/google/android/exoplayer2/source/dash/manifest/DashManifestTest.java rename to library/dash/src/test/java/com/google/android/exoplayer2/source/dash/manifest/DashManifestTest.java index c6060a7caa..9cf3594116 100644 --- a/library/dash/src/androidTest/java/com/google/android/exoplayer2/source/dash/manifest/DashManifestTest.java +++ b/library/dash/src/test/java/com/google/android/exoplayer2/source/dash/manifest/DashManifestTest.java @@ -24,109 +24,143 @@ import java.util.Arrays; import java.util.Collections; import java.util.List; import java.util.Random; -import junit.framework.TestCase; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.robolectric.RobolectricTestRunner; -/** - * Unit tests for {@link DashManifest}. - */ -public class DashManifestTest extends TestCase { +/** Unit tests for {@link DashManifest}. */ +@RunWith(RobolectricTestRunner.class) +public class DashManifestTest { private static final UtcTimingElement DUMMY_UTC_TIMING = new UtcTimingElement("", ""); private static final SingleSegmentBase DUMMY_SEGMENT_BASE = new SingleSegmentBase(); private static final Format DUMMY_FORMAT = Format.createSampleFormat("", "", 0); + @Test public void testCopy() throws Exception { Representation[][][] representations = newRepresentations(3, 2, 3); - DashManifest sourceManifest = newDashManifest(10, - newPeriod("1", 1, - newAdaptationSet(2, representations[0][0]), - newAdaptationSet(3, representations[0][1])), - newPeriod("4", 4, - newAdaptationSet(5, representations[1][0]), - newAdaptationSet(6, representations[1][1])), - newPeriod("7", 7, - newAdaptationSet(8, representations[2][0]), - newAdaptationSet(9, representations[2][1]))); + DashManifest sourceManifest = + newDashManifest( + 10, + newPeriod( + "1", + 1, + newAdaptationSet(2, representations[0][0]), + newAdaptationSet(3, representations[0][1])), + newPeriod( + "4", + 4, + newAdaptationSet(5, representations[1][0]), + newAdaptationSet(6, representations[1][1])), + newPeriod( + "7", + 7, + newAdaptationSet(8, representations[2][0]), + newAdaptationSet(9, representations[2][1]))); - List keys = Arrays.asList( - new RepresentationKey(0, 0, 0), - new RepresentationKey(0, 0, 1), - new RepresentationKey(0, 1, 2), - - new RepresentationKey(1, 0, 1), - new RepresentationKey(1, 1, 0), - new RepresentationKey(1, 1, 2), - - new RepresentationKey(2, 0, 1), - new RepresentationKey(2, 0, 2), - new RepresentationKey(2, 1, 0)); + List keys = + Arrays.asList( + new RepresentationKey(0, 0, 0), + new RepresentationKey(0, 0, 1), + new RepresentationKey(0, 1, 2), + new RepresentationKey(1, 0, 1), + new RepresentationKey(1, 1, 0), + new RepresentationKey(1, 1, 2), + new RepresentationKey(2, 0, 1), + new RepresentationKey(2, 0, 2), + new RepresentationKey(2, 1, 0)); // Keys don't need to be in any particular order Collections.shuffle(keys, new Random(0)); DashManifest copyManifest = sourceManifest.copy(keys); - DashManifest expectedManifest = newDashManifest(10, - newPeriod("1", 1, - newAdaptationSet(2, representations[0][0][0], representations[0][0][1]), - newAdaptationSet(3, representations[0][1][2])), - newPeriod("4", 4, - newAdaptationSet(5, representations[1][0][1]), - newAdaptationSet(6, representations[1][1][0], representations[1][1][2])), - newPeriod("7", 7, - newAdaptationSet(8, representations[2][0][1], representations[2][0][2]), - newAdaptationSet(9, representations[2][1][0]))); + DashManifest expectedManifest = + newDashManifest( + 10, + newPeriod( + "1", + 1, + newAdaptationSet(2, representations[0][0][0], representations[0][0][1]), + newAdaptationSet(3, representations[0][1][2])), + newPeriod( + "4", + 4, + newAdaptationSet(5, representations[1][0][1]), + newAdaptationSet(6, representations[1][1][0], representations[1][1][2])), + newPeriod( + "7", + 7, + newAdaptationSet(8, representations[2][0][1], representations[2][0][2]), + newAdaptationSet(9, representations[2][1][0]))); assertManifestEquals(expectedManifest, copyManifest); } + @Test public void testCopySameAdaptationIndexButDifferentPeriod() throws Exception { Representation[][][] representations = newRepresentations(2, 1, 1); - DashManifest sourceManifest = newDashManifest(10, - newPeriod("1", 1, - newAdaptationSet(2, representations[0][0])), - newPeriod("4", 4, - newAdaptationSet(5, representations[1][0]))); + DashManifest sourceManifest = + newDashManifest( + 10, + newPeriod("1", 1, newAdaptationSet(2, representations[0][0])), + newPeriod("4", 4, newAdaptationSet(5, representations[1][0]))); - DashManifest copyManifest = sourceManifest.copy(Arrays.asList( - new RepresentationKey(0, 0, 0), - new RepresentationKey(1, 0, 0))); + DashManifest copyManifest = + sourceManifest.copy( + Arrays.asList(new RepresentationKey(0, 0, 0), new RepresentationKey(1, 0, 0))); - DashManifest expectedManifest = newDashManifest(10, - newPeriod("1", 1, - newAdaptationSet(2, representations[0][0])), - newPeriod("4", 4, - newAdaptationSet(5, representations[1][0]))); + DashManifest expectedManifest = + newDashManifest( + 10, + newPeriod("1", 1, newAdaptationSet(2, representations[0][0])), + newPeriod("4", 4, newAdaptationSet(5, representations[1][0]))); assertManifestEquals(expectedManifest, copyManifest); } + @Test public void testCopySkipPeriod() throws Exception { Representation[][][] representations = newRepresentations(3, 2, 3); - DashManifest sourceManifest = newDashManifest(10, - newPeriod("1", 1, - newAdaptationSet(2, representations[0][0]), - newAdaptationSet(3, representations[0][1])), - newPeriod("4", 4, - newAdaptationSet(5, representations[1][0]), - newAdaptationSet(6, representations[1][1])), - newPeriod("7", 7, - newAdaptationSet(8, representations[2][0]), - newAdaptationSet(9, representations[2][1]))); + DashManifest sourceManifest = + newDashManifest( + 10, + newPeriod( + "1", + 1, + newAdaptationSet(2, representations[0][0]), + newAdaptationSet(3, representations[0][1])), + newPeriod( + "4", + 4, + newAdaptationSet(5, representations[1][0]), + newAdaptationSet(6, representations[1][1])), + newPeriod( + "7", + 7, + newAdaptationSet(8, representations[2][0]), + newAdaptationSet(9, representations[2][1]))); - DashManifest copyManifest = sourceManifest.copy(Arrays.asList( - new RepresentationKey(0, 0, 0), - new RepresentationKey(0, 0, 1), - new RepresentationKey(0, 1, 2), + DashManifest copyManifest = + sourceManifest.copy( + Arrays.asList( + new RepresentationKey(0, 0, 0), + new RepresentationKey(0, 0, 1), + new RepresentationKey(0, 1, 2), + new RepresentationKey(2, 0, 1), + new RepresentationKey(2, 0, 2), + new RepresentationKey(2, 1, 0))); - new RepresentationKey(2, 0, 1), - new RepresentationKey(2, 0, 2), - new RepresentationKey(2, 1, 0))); - - DashManifest expectedManifest = newDashManifest(7, - newPeriod("1", 1, - newAdaptationSet(2, representations[0][0][0], representations[0][0][1]), - newAdaptationSet(3, representations[0][1][2])), - newPeriod("7", 4, - newAdaptationSet(8, representations[2][0][1], representations[2][0][2]), - newAdaptationSet(9, representations[2][1][0]))); + DashManifest expectedManifest = + newDashManifest( + 7, + newPeriod( + "1", + 1, + newAdaptationSet(2, representations[0][0][0], representations[0][0][1]), + newAdaptationSet(3, representations[0][1][2])), + newPeriod( + "7", + 4, + newAdaptationSet(8, representations[2][0][1], representations[2][0][2]), + newAdaptationSet(9, representations[2][1][0]))); assertManifestEquals(expectedManifest, copyManifest); } @@ -164,8 +198,8 @@ public class DashManifestTest extends TestCase { } } - private static Representation[][][] newRepresentations(int periodCount, int adaptationSetCounts, - int representationCounts) { + private static Representation[][][] newRepresentations( + int periodCount, int adaptationSetCounts, int representationCounts) { Representation[][][] representations = new Representation[periodCount][][]; for (int i = 0; i < periodCount; i++) { representations[i] = new Representation[adaptationSetCounts][]; @@ -184,8 +218,8 @@ public class DashManifestTest extends TestCase { } private static DashManifest newDashManifest(int duration, Period... periods) { - return new DashManifest(0, duration, 1, false, 2, 3, 4, 12345, DUMMY_UTC_TIMING, Uri.EMPTY, - Arrays.asList(periods)); + return new DashManifest( + 0, duration, 1, false, 2, 3, 4, 12345, DUMMY_UTC_TIMING, Uri.EMPTY, Arrays.asList(periods)); } private static Period newPeriod(String id, int startMs, AdaptationSet... adaptationSets) { @@ -195,5 +229,4 @@ public class DashManifestTest extends TestCase { private static AdaptationSet newAdaptationSet(int seed, Representation... representations) { return new AdaptationSet(++seed, ++seed, Arrays.asList(representations), null, null); } - } diff --git a/library/dash/src/androidTest/java/com/google/android/exoplayer2/source/dash/manifest/RangedUriTest.java b/library/dash/src/test/java/com/google/android/exoplayer2/source/dash/manifest/RangedUriTest.java similarity index 92% rename from library/dash/src/androidTest/java/com/google/android/exoplayer2/source/dash/manifest/RangedUriTest.java rename to library/dash/src/test/java/com/google/android/exoplayer2/source/dash/manifest/RangedUriTest.java index 4073625cd1..16c9a4706e 100644 --- a/library/dash/src/androidTest/java/com/google/android/exoplayer2/source/dash/manifest/RangedUriTest.java +++ b/library/dash/src/test/java/com/google/android/exoplayer2/source/dash/manifest/RangedUriTest.java @@ -18,17 +18,19 @@ package com.google.android.exoplayer2.source.dash.manifest; import static com.google.common.truth.Truth.assertThat; import com.google.android.exoplayer2.C; -import junit.framework.TestCase; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.robolectric.RobolectricTestRunner; -/** - * Unit test for {@link RangedUri}. - */ -public class RangedUriTest extends TestCase { +/** Unit test for {@link RangedUri}. */ +@RunWith(RobolectricTestRunner.class) +public class RangedUriTest { private static final String BASE_URI = "http://www.test.com/"; private static final String PARTIAL_URI = "path/file.ext"; private static final String FULL_URI = BASE_URI + PARTIAL_URI; + @Test public void testMerge() { RangedUri rangeA = new RangedUri(FULL_URI, 0, 10); RangedUri rangeB = new RangedUri(FULL_URI, 10, 10); @@ -36,6 +38,7 @@ public class RangedUriTest extends TestCase { assertMerge(rangeA, rangeB, expected, null); } + @Test public void testMergeUnbounded() { RangedUri rangeA = new RangedUri(FULL_URI, 0, 10); RangedUri rangeB = new RangedUri(FULL_URI, 10, C.LENGTH_UNSET); @@ -43,6 +46,7 @@ public class RangedUriTest extends TestCase { assertMerge(rangeA, rangeB, expected, null); } + @Test public void testNonMerge() { // A and B do not overlap, so should not merge RangedUri rangeA = new RangedUri(FULL_URI, 0, 10); @@ -65,6 +69,7 @@ public class RangedUriTest extends TestCase { assertNonMerge(rangeA, rangeB, null); } + @Test public void testMergeWithBaseUri() { RangedUri rangeA = new RangedUri(PARTIAL_URI, 0, 10); RangedUri rangeB = new RangedUri(FULL_URI, 10, 10); @@ -85,5 +90,4 @@ public class RangedUriTest extends TestCase { merged = rangeB.attemptMerge(rangeA, baseUrl); assertThat(merged).isNull(); } - } diff --git a/library/dash/src/androidTest/java/com/google/android/exoplayer2/source/dash/manifest/RepresentationTest.java b/library/dash/src/test/java/com/google/android/exoplayer2/source/dash/manifest/RepresentationTest.java similarity index 54% rename from library/dash/src/androidTest/java/com/google/android/exoplayer2/source/dash/manifest/RepresentationTest.java rename to library/dash/src/test/java/com/google/android/exoplayer2/source/dash/manifest/RepresentationTest.java index 2ceb0f0506..309e6c8eb0 100644 --- a/library/dash/src/androidTest/java/com/google/android/exoplayer2/source/dash/manifest/RepresentationTest.java +++ b/library/dash/src/test/java/com/google/android/exoplayer2/source/dash/manifest/RepresentationTest.java @@ -20,27 +20,49 @@ import static com.google.common.truth.Truth.assertThat; import com.google.android.exoplayer2.Format; import com.google.android.exoplayer2.source.dash.manifest.SegmentBase.SingleSegmentBase; import com.google.android.exoplayer2.util.MimeTypes; -import junit.framework.TestCase; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.robolectric.RobolectricTestRunner; -/** - * Unit test for {@link Representation}. - */ -public class RepresentationTest extends TestCase { +/** Unit test for {@link Representation}. */ +@RunWith(RobolectricTestRunner.class) +public class RepresentationTest { + @Test public void testGetCacheKey() { String uri = "http://www.google.com"; SegmentBase base = new SingleSegmentBase(new RangedUri(null, 0, 1), 1, 0, 1, 1); - Format format = Format.createVideoContainerFormat("0", MimeTypes.APPLICATION_MP4, null, - MimeTypes.VIDEO_H264, 2500000, 1920, 1080, Format.NO_VALUE, null, 0); - Representation representation = Representation.newInstance("test_stream_1", 3, format, uri, - base); + Format format = + Format.createVideoContainerFormat( + "0", + MimeTypes.APPLICATION_MP4, + null, + MimeTypes.VIDEO_H264, + 2500000, + 1920, + 1080, + Format.NO_VALUE, + null, + 0); + Representation representation = + Representation.newInstance("test_stream_1", 3, format, uri, base); assertThat(representation.getCacheKey()).isEqualTo("test_stream_1.0.3"); - format = Format.createVideoContainerFormat("150", MimeTypes.APPLICATION_MP4, null, - MimeTypes.VIDEO_H264, 2500000, 1920, 1080, Format.NO_VALUE, null, 0); - representation = Representation.newInstance("test_stream_1", Representation.REVISION_ID_DEFAULT, - format, uri, base); + format = + Format.createVideoContainerFormat( + "150", + MimeTypes.APPLICATION_MP4, + null, + MimeTypes.VIDEO_H264, + 2500000, + 1920, + 1080, + Format.NO_VALUE, + null, + 0); + representation = + Representation.newInstance( + "test_stream_1", Representation.REVISION_ID_DEFAULT, format, uri, base); assertThat(representation.getCacheKey()).isEqualTo("test_stream_1.150.-1"); } - } diff --git a/library/dash/src/androidTest/java/com/google/android/exoplayer2/source/dash/manifest/UrlTemplateTest.java b/library/dash/src/test/java/com/google/android/exoplayer2/source/dash/manifest/UrlTemplateTest.java similarity index 89% rename from library/dash/src/androidTest/java/com/google/android/exoplayer2/source/dash/manifest/UrlTemplateTest.java rename to library/dash/src/test/java/com/google/android/exoplayer2/source/dash/manifest/UrlTemplateTest.java index b3221bbc18..4192280c81 100644 --- a/library/dash/src/androidTest/java/com/google/android/exoplayer2/source/dash/manifest/UrlTemplateTest.java +++ b/library/dash/src/test/java/com/google/android/exoplayer2/source/dash/manifest/UrlTemplateTest.java @@ -16,14 +16,17 @@ package com.google.android.exoplayer2.source.dash.manifest; import static com.google.common.truth.Truth.assertThat; +import static org.junit.Assert.fail; -import junit.framework.TestCase; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.robolectric.RobolectricTestRunner; -/** - * Unit test for {@link UrlTemplate}. - */ -public class UrlTemplateTest extends TestCase { +/** Unit test for {@link UrlTemplate}. */ +@RunWith(RobolectricTestRunner.class) +public class UrlTemplateTest { + @Test public void testRealExamples() { String template = "QualityLevels($Bandwidth$)/Fragments(video=$Time$,format=mpd-time-csf)"; UrlTemplate urlTemplate = UrlTemplate.compile(template); @@ -41,6 +44,7 @@ public class UrlTemplateTest extends TestCase { assertThat(url).isEqualTo("chunk_ctvideo_cfm4s_ridabc1_cn10_w2073857842_mpd.m4s"); } + @Test public void testFull() { String template = "$Bandwidth$_a_$RepresentationID$_b_$Time$_c_$Number$"; UrlTemplate urlTemplate = UrlTemplate.compile(template); @@ -48,6 +52,7 @@ public class UrlTemplateTest extends TestCase { assertThat(url).isEqualTo("650000_a_abc1_b_5000_c_10"); } + @Test public void testFullWithDollarEscaping() { String template = "$$$Bandwidth$$$_a$$_$RepresentationID$_b_$Time$_c_$Number$$$"; UrlTemplate urlTemplate = UrlTemplate.compile(template); @@ -55,6 +60,7 @@ public class UrlTemplateTest extends TestCase { assertThat(url).isEqualTo("$650000$_a$_abc1_b_5000_c_10$"); } + @Test public void testInvalidSubstitution() { String template = "$IllegalId$"; try { @@ -64,5 +70,4 @@ public class UrlTemplateTest extends TestCase { // Expected. } } - } diff --git a/library/dash/src/test/java/com/google/android/exoplayer2/source/dash/offline/DashDownloadTestData.java b/library/dash/src/test/java/com/google/android/exoplayer2/source/dash/offline/DashDownloadTestData.java new file mode 100644 index 0000000000..a215347f15 --- /dev/null +++ b/library/dash/src/test/java/com/google/android/exoplayer2/source/dash/offline/DashDownloadTestData.java @@ -0,0 +1,103 @@ +/* + * Copyright (C) 2017 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.exoplayer2.source.dash.offline; + +import android.net.Uri; +import com.google.android.exoplayer2.C; +import java.nio.charset.Charset; + +/** Data for DASH downloading tests. */ +/* package */ interface DashDownloadTestData { + + Uri TEST_MPD_URI = Uri.parse("test.mpd"); + + byte[] TEST_MPD = + ("\n" + + "\n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + // Bounded range data + + " \n" + // Unbounded range data + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + // This segment list has a 1 second offset to make sure the progressive download order + + " \n" + + " \n" + + " \n" // 1s offset + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + "") + .getBytes(Charset.forName(C.UTF8_NAME)); + + byte[] TEST_MPD_NO_INDEX = + ("\n" + + "\n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + "") + .getBytes(Charset.forName(C.UTF8_NAME)); +} diff --git a/library/dash/src/androidTest/java/com/google/android/exoplayer2/source/dash/offline/DashDownloaderTest.java b/library/dash/src/test/java/com/google/android/exoplayer2/source/dash/offline/DashDownloaderTest.java similarity index 65% rename from library/dash/src/androidTest/java/com/google/android/exoplayer2/source/dash/offline/DashDownloaderTest.java rename to library/dash/src/test/java/com/google/android/exoplayer2/source/dash/offline/DashDownloaderTest.java index 5e7ce43ebf..d00b24e84f 100644 --- a/library/dash/src/androidTest/java/com/google/android/exoplayer2/source/dash/offline/DashDownloaderTest.java +++ b/library/dash/src/test/java/com/google/android/exoplayer2/source/dash/offline/DashDownloaderTest.java @@ -22,10 +22,10 @@ import static com.google.android.exoplayer2.testutil.CacheAsserts.assertCacheEmp import static com.google.android.exoplayer2.testutil.CacheAsserts.assertCachedData; import static com.google.android.exoplayer2.testutil.CacheAsserts.assertDataCached; import static com.google.common.truth.Truth.assertThat; +import static org.junit.Assert.fail; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.when; -import android.test.InstrumentationTestCase; import com.google.android.exoplayer2.C; import com.google.android.exoplayer2.offline.DownloadException; import com.google.android.exoplayer2.offline.Downloader.ProgressListener; @@ -35,7 +35,6 @@ import com.google.android.exoplayer2.source.dash.manifest.RepresentationKey; import com.google.android.exoplayer2.testutil.FakeDataSet; import com.google.android.exoplayer2.testutil.FakeDataSource; import com.google.android.exoplayer2.testutil.FakeDataSource.Factory; -import com.google.android.exoplayer2.testutil.MockitoUtil; import com.google.android.exoplayer2.testutil.TestUtil; import com.google.android.exoplayer2.upstream.DataSpec; import com.google.android.exoplayer2.upstream.cache.NoOpCacheEvictor; @@ -44,34 +43,38 @@ import com.google.android.exoplayer2.util.Util; import java.io.File; import java.io.IOException; import java.util.Arrays; +import org.junit.After; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; import org.mockito.InOrder; import org.mockito.Mockito; +import org.mockito.MockitoAnnotations; +import org.robolectric.RobolectricTestRunner; +import org.robolectric.RuntimeEnvironment; -/** - * Unit tests for {@link DashDownloader}. - */ -public class DashDownloaderTest extends InstrumentationTestCase { +/** Unit tests for {@link DashDownloader}. */ +@RunWith(RobolectricTestRunner.class) +public class DashDownloaderTest { private SimpleCache cache; private File tempFolder; - @Override + @Before public void setUp() throws Exception { - super.setUp(); - MockitoUtil.setUpMockito(this); - tempFolder = Util.createTempDirectory(getInstrumentation().getContext(), "ExoPlayerTest"); + MockitoAnnotations.initMocks(this); + tempFolder = Util.createTempDirectory(RuntimeEnvironment.application, "ExoPlayerTest"); cache = new SimpleCache(tempFolder, new NoOpCacheEvictor()); } - @Override + @After public void tearDown() throws Exception { Util.recursiveDelete(tempFolder); - super.tearDown(); } + @Test public void testGetManifest() throws Exception { - FakeDataSet fakeDataSet = new FakeDataSet() - .setData(TEST_MPD_URI, TEST_MPD); + FakeDataSet fakeDataSet = new FakeDataSet().setData(TEST_MPD_URI, TEST_MPD); DashDownloader dashDownloader = getDashDownloader(fakeDataSet); DashManifest manifest = dashDownloader.getManifest(); @@ -80,15 +83,17 @@ public class DashDownloaderTest extends InstrumentationTestCase { assertCachedData(cache, fakeDataSet); } + @Test public void testDownloadManifestFailure() throws Exception { byte[] testMpdFirstPart = Arrays.copyOf(TEST_MPD, 10); byte[] testMpdSecondPart = Arrays.copyOfRange(TEST_MPD, 10, TEST_MPD.length); - FakeDataSet fakeDataSet = new FakeDataSet() - .newData(TEST_MPD_URI) - .appendReadData(testMpdFirstPart) - .appendReadError(new IOException()) - .appendReadData(testMpdSecondPart) - .endData(); + FakeDataSet fakeDataSet = + new FakeDataSet() + .newData(TEST_MPD_URI) + .appendReadData(testMpdFirstPart) + .appendReadError(new IOException()) + .appendReadData(testMpdSecondPart) + .endData(); DashDownloader dashDownloader = getDashDownloader(fakeDataSet); // fails on the first try @@ -108,13 +113,15 @@ public class DashDownloaderTest extends InstrumentationTestCase { assertCachedData(cache, fakeDataSet); } + @Test public void testDownloadRepresentation() throws Exception { - FakeDataSet fakeDataSet = new FakeDataSet() - .setData(TEST_MPD_URI, TEST_MPD) - .setRandomData("audio_init_data", 10) - .setRandomData("audio_segment_1", 4) - .setRandomData("audio_segment_2", 5) - .setRandomData("audio_segment_3", 6); + FakeDataSet fakeDataSet = + new FakeDataSet() + .setData(TEST_MPD_URI, TEST_MPD) + .setRandomData("audio_init_data", 10) + .setRandomData("audio_segment_1", 4) + .setRandomData("audio_segment_2", 5) + .setRandomData("audio_segment_3", 6); DashDownloader dashDownloader = getDashDownloader(fakeDataSet); dashDownloader.selectRepresentations(new RepresentationKey[] {new RepresentationKey(0, 0, 0)}); @@ -123,17 +130,19 @@ public class DashDownloaderTest extends InstrumentationTestCase { assertCachedData(cache, fakeDataSet); } + @Test public void testDownloadRepresentationInSmallParts() throws Exception { - FakeDataSet fakeDataSet = new FakeDataSet() - .setData(TEST_MPD_URI, TEST_MPD) - .setRandomData("audio_init_data", 10) - .newData("audio_segment_1") - .appendReadData(TestUtil.buildTestData(10)) - .appendReadData(TestUtil.buildTestData(10)) - .appendReadData(TestUtil.buildTestData(10)) - .endData() - .setRandomData("audio_segment_2", 5) - .setRandomData("audio_segment_3", 6); + FakeDataSet fakeDataSet = + new FakeDataSet() + .setData(TEST_MPD_URI, TEST_MPD) + .setRandomData("audio_init_data", 10) + .newData("audio_segment_1") + .appendReadData(TestUtil.buildTestData(10)) + .appendReadData(TestUtil.buildTestData(10)) + .appendReadData(TestUtil.buildTestData(10)) + .endData() + .setRandomData("audio_segment_2", 5) + .setRandomData("audio_segment_3", 6); DashDownloader dashDownloader = getDashDownloader(fakeDataSet); dashDownloader.selectRepresentations(new RepresentationKey[] {new RepresentationKey(0, 0, 0)}); @@ -142,16 +151,18 @@ public class DashDownloaderTest extends InstrumentationTestCase { assertCachedData(cache, fakeDataSet); } + @Test public void testDownloadRepresentations() throws Exception { - FakeDataSet fakeDataSet = new FakeDataSet() - .setData(TEST_MPD_URI, TEST_MPD) - .setRandomData("audio_init_data", 10) - .setRandomData("audio_segment_1", 4) - .setRandomData("audio_segment_2", 5) - .setRandomData("audio_segment_3", 6) - .setRandomData("text_segment_1", 1) - .setRandomData("text_segment_2", 2) - .setRandomData("text_segment_3", 3); + FakeDataSet fakeDataSet = + new FakeDataSet() + .setData(TEST_MPD_URI, TEST_MPD) + .setRandomData("audio_init_data", 10) + .setRandomData("audio_segment_1", 4) + .setRandomData("audio_segment_2", 5) + .setRandomData("audio_segment_3", 6) + .setRandomData("text_segment_1", 1) + .setRandomData("text_segment_2", 2) + .setRandomData("text_segment_3", 3); DashDownloader dashDownloader = getDashDownloader(fakeDataSet); dashDownloader.selectRepresentations( @@ -161,19 +172,21 @@ public class DashDownloaderTest extends InstrumentationTestCase { assertCachedData(cache, fakeDataSet); } + @Test public void testDownloadAllRepresentations() throws Exception { - FakeDataSet fakeDataSet = new FakeDataSet() - .setData(TEST_MPD_URI, TEST_MPD) - .setRandomData("audio_init_data", 10) - .setRandomData("audio_segment_1", 4) - .setRandomData("audio_segment_2", 5) - .setRandomData("audio_segment_3", 6) - .setRandomData("text_segment_1", 1) - .setRandomData("text_segment_2", 2) - .setRandomData("text_segment_3", 3) - .setRandomData("period_2_segment_1", 1) - .setRandomData("period_2_segment_2", 2) - .setRandomData("period_2_segment_3", 3); + FakeDataSet fakeDataSet = + new FakeDataSet() + .setData(TEST_MPD_URI, TEST_MPD) + .setRandomData("audio_init_data", 10) + .setRandomData("audio_segment_1", 4) + .setRandomData("audio_segment_2", 5) + .setRandomData("audio_segment_3", 6) + .setRandomData("text_segment_1", 1) + .setRandomData("text_segment_2", 2) + .setRandomData("text_segment_3", 3) + .setRandomData("period_2_segment_1", 1) + .setRandomData("period_2_segment_2", 2) + .setRandomData("period_2_segment_3", 3); DashDownloader dashDownloader = getDashDownloader(fakeDataSet); // dashDownloader.selectRepresentations() isn't called @@ -195,21 +208,23 @@ public class DashDownloaderTest extends InstrumentationTestCase { dashDownloader.remove(); } + @Test public void testProgressiveDownload() throws Exception { - FakeDataSet fakeDataSet = new FakeDataSet() - .setData(TEST_MPD_URI, TEST_MPD) - .setRandomData("audio_init_data", 10) - .setRandomData("audio_segment_1", 4) - .setRandomData("audio_segment_2", 5) - .setRandomData("audio_segment_3", 6) - .setRandomData("text_segment_1", 1) - .setRandomData("text_segment_2", 2) - .setRandomData("text_segment_3", 3); + FakeDataSet fakeDataSet = + new FakeDataSet() + .setData(TEST_MPD_URI, TEST_MPD) + .setRandomData("audio_init_data", 10) + .setRandomData("audio_segment_1", 4) + .setRandomData("audio_segment_2", 5) + .setRandomData("audio_segment_3", 6) + .setRandomData("text_segment_1", 1) + .setRandomData("text_segment_2", 2) + .setRandomData("text_segment_3", 3); FakeDataSource fakeDataSource = new FakeDataSource(fakeDataSet); Factory factory = mock(Factory.class); when(factory.createDataSource()).thenReturn(fakeDataSource); - DashDownloader dashDownloader = new DashDownloader(TEST_MPD_URI, - new DownloaderConstructorHelper(cache, factory)); + DashDownloader dashDownloader = + new DashDownloader(TEST_MPD_URI, new DownloaderConstructorHelper(cache, factory)); dashDownloader.selectRepresentations( new RepresentationKey[] {new RepresentationKey(0, 0, 0), new RepresentationKey(0, 1, 0)}); @@ -227,21 +242,23 @@ public class DashDownloaderTest extends InstrumentationTestCase { assertThat(openedDataSpecs[7].uri.getPath()).isEqualTo("text_segment_3"); } + @Test public void testProgressiveDownloadSeparatePeriods() throws Exception { - FakeDataSet fakeDataSet = new FakeDataSet() - .setData(TEST_MPD_URI, TEST_MPD) - .setRandomData("audio_init_data", 10) - .setRandomData("audio_segment_1", 4) - .setRandomData("audio_segment_2", 5) - .setRandomData("audio_segment_3", 6) - .setRandomData("period_2_segment_1", 1) - .setRandomData("period_2_segment_2", 2) - .setRandomData("period_2_segment_3", 3); + FakeDataSet fakeDataSet = + new FakeDataSet() + .setData(TEST_MPD_URI, TEST_MPD) + .setRandomData("audio_init_data", 10) + .setRandomData("audio_segment_1", 4) + .setRandomData("audio_segment_2", 5) + .setRandomData("audio_segment_3", 6) + .setRandomData("period_2_segment_1", 1) + .setRandomData("period_2_segment_2", 2) + .setRandomData("period_2_segment_3", 3); FakeDataSource fakeDataSource = new FakeDataSource(fakeDataSet); Factory factory = mock(Factory.class); when(factory.createDataSource()).thenReturn(fakeDataSource); - DashDownloader dashDownloader = new DashDownloader(TEST_MPD_URI, - new DownloaderConstructorHelper(cache, factory)); + DashDownloader dashDownloader = + new DashDownloader(TEST_MPD_URI, new DownloaderConstructorHelper(cache, factory)); dashDownloader.selectRepresentations( new RepresentationKey[] {new RepresentationKey(0, 0, 0), new RepresentationKey(1, 0, 0)}); @@ -259,17 +276,19 @@ public class DashDownloaderTest extends InstrumentationTestCase { assertThat(openedDataSpecs[7].uri.getPath()).isEqualTo("period_2_segment_3"); } + @Test public void testDownloadRepresentationFailure() throws Exception { - FakeDataSet fakeDataSet = new FakeDataSet() - .setData(TEST_MPD_URI, TEST_MPD) - .setRandomData("audio_init_data", 10) - .setRandomData("audio_segment_1", 4) - .newData("audio_segment_2") - .appendReadData(TestUtil.buildTestData(2)) - .appendReadError(new IOException()) - .appendReadData(TestUtil.buildTestData(3)) - .endData() - .setRandomData("audio_segment_3", 6); + FakeDataSet fakeDataSet = + new FakeDataSet() + .setData(TEST_MPD_URI, TEST_MPD) + .setRandomData("audio_init_data", 10) + .setRandomData("audio_segment_1", 4) + .newData("audio_segment_2") + .appendReadData(TestUtil.buildTestData(2)) + .appendReadError(new IOException()) + .appendReadData(TestUtil.buildTestData(3)) + .endData() + .setRandomData("audio_segment_3", 6); DashDownloader dashDownloader = getDashDownloader(fakeDataSet); dashDownloader.selectRepresentations(new RepresentationKey[] {new RepresentationKey(0, 0, 0)}); @@ -285,17 +304,19 @@ public class DashDownloaderTest extends InstrumentationTestCase { assertCachedData(cache, fakeDataSet); } + @Test public void testCounters() throws Exception { - FakeDataSet fakeDataSet = new FakeDataSet() - .setData(TEST_MPD_URI, TEST_MPD) - .setRandomData("audio_init_data", 10) - .setRandomData("audio_segment_1", 4) - .newData("audio_segment_2") - .appendReadData(TestUtil.buildTestData(2)) - .appendReadError(new IOException()) - .appendReadData(TestUtil.buildTestData(3)) - .endData() - .setRandomData("audio_segment_3", 6); + FakeDataSet fakeDataSet = + new FakeDataSet() + .setData(TEST_MPD_URI, TEST_MPD) + .setRandomData("audio_init_data", 10) + .setRandomData("audio_segment_1", 4) + .newData("audio_segment_2") + .appendReadData(TestUtil.buildTestData(2)) + .appendReadError(new IOException()) + .appendReadData(TestUtil.buildTestData(3)) + .endData() + .setRandomData("audio_segment_3", 6); DashDownloader dashDownloader = getDashDownloader(fakeDataSet); assertCounters(dashDownloader, C.LENGTH_UNSET, C.LENGTH_UNSET, C.LENGTH_UNSET); @@ -319,13 +340,15 @@ public class DashDownloaderTest extends InstrumentationTestCase { assertCounters(dashDownloader, 4, 4, 10 + 4 + 5 + 6); } + @Test public void testListener() throws Exception { - FakeDataSet fakeDataSet = new FakeDataSet() - .setData(TEST_MPD_URI, TEST_MPD) - .setRandomData("audio_init_data", 10) - .setRandomData("audio_segment_1", 4) - .setRandomData("audio_segment_2", 5) - .setRandomData("audio_segment_3", 6); + FakeDataSet fakeDataSet = + new FakeDataSet() + .setData(TEST_MPD_URI, TEST_MPD) + .setRandomData("audio_init_data", 10) + .setRandomData("audio_segment_1", 4) + .setRandomData("audio_segment_2", 5) + .setRandomData("audio_segment_3", 6); DashDownloader dashDownloader = getDashDownloader(fakeDataSet); dashDownloader.selectRepresentations(new RepresentationKey[] {new RepresentationKey(0, 0, 0)}); @@ -340,16 +363,18 @@ public class DashDownloaderTest extends InstrumentationTestCase { inOrder.verifyNoMoreInteractions(); } + @Test public void testRemoveAll() throws Exception { - FakeDataSet fakeDataSet = new FakeDataSet() - .setData(TEST_MPD_URI, TEST_MPD) - .setRandomData("audio_init_data", 10) - .setRandomData("audio_segment_1", 4) - .setRandomData("audio_segment_2", 5) - .setRandomData("audio_segment_3", 6) - .setRandomData("text_segment_1", 1) - .setRandomData("text_segment_2", 2) - .setRandomData("text_segment_3", 3); + FakeDataSet fakeDataSet = + new FakeDataSet() + .setData(TEST_MPD_URI, TEST_MPD) + .setRandomData("audio_init_data", 10) + .setRandomData("audio_segment_1", 4) + .setRandomData("audio_segment_2", 5) + .setRandomData("audio_segment_3", 6) + .setRandomData("text_segment_1", 1) + .setRandomData("text_segment_2", 2) + .setRandomData("text_segment_3", 3); DashDownloader dashDownloader = getDashDownloader(fakeDataSet); dashDownloader.selectRepresentations( new RepresentationKey[] {new RepresentationKey(0, 0, 0), new RepresentationKey(0, 1, 0)}); @@ -360,10 +385,12 @@ public class DashDownloaderTest extends InstrumentationTestCase { assertCacheEmpty(cache); } + @Test public void testRepresentationWithoutIndex() throws Exception { - FakeDataSet fakeDataSet = new FakeDataSet() - .setData(TEST_MPD_URI, TEST_MPD_NO_INDEX) - .setRandomData("test_segment_1", 4); + FakeDataSet fakeDataSet = + new FakeDataSet() + .setData(TEST_MPD_URI, TEST_MPD_NO_INDEX) + .setRandomData("test_segment_1", 4); DashDownloader dashDownloader = getDashDownloader(fakeDataSet); dashDownloader.selectRepresentations(new RepresentationKey[] {new RepresentationKey(0, 0, 0)}); @@ -379,13 +406,15 @@ public class DashDownloaderTest extends InstrumentationTestCase { assertCacheEmpty(cache); } + @Test public void testSelectRepresentationsClearsPreviousSelection() throws Exception { - FakeDataSet fakeDataSet = new FakeDataSet() - .setData(TEST_MPD_URI, TEST_MPD) - .setRandomData("audio_init_data", 10) - .setRandomData("audio_segment_1", 4) - .setRandomData("audio_segment_2", 5) - .setRandomData("audio_segment_3", 6); + FakeDataSet fakeDataSet = + new FakeDataSet() + .setData(TEST_MPD_URI, TEST_MPD) + .setRandomData("audio_init_data", 10) + .setRandomData("audio_segment_1", 4) + .setRandomData("audio_segment_2", 5) + .setRandomData("audio_segment_3", 6); DashDownloader dashDownloader = getDashDownloader(fakeDataSet); dashDownloader.selectRepresentations( @@ -401,11 +430,13 @@ public class DashDownloaderTest extends InstrumentationTestCase { return new DashDownloader(TEST_MPD_URI, new DownloaderConstructorHelper(cache, factory)); } - private static void assertCounters(DashDownloader dashDownloader, int totalSegments, - int downloadedSegments, int downloadedBytes) { + private static void assertCounters( + DashDownloader dashDownloader, + int totalSegments, + int downloadedSegments, + int downloadedBytes) { assertThat(dashDownloader.getTotalSegments()).isEqualTo(totalSegments); assertThat(dashDownloader.getDownloadedSegments()).isEqualTo(downloadedSegments); assertThat(dashDownloader.getDownloadedBytes()).isEqualTo(downloadedBytes); } - } diff --git a/library/dash/src/test/resources/robolectric.properties b/library/dash/src/test/resources/robolectric.properties new file mode 100644 index 0000000000..2f3210368e --- /dev/null +++ b/library/dash/src/test/resources/robolectric.properties @@ -0,0 +1 @@ +manifest=src/test/AndroidManifest.xml diff --git a/library/hls/build.gradle b/library/hls/build.gradle index 5471eacec6..c2268a3007 100644 --- a/library/hls/build.gradle +++ b/library/hls/build.gradle @@ -33,12 +33,9 @@ android { } dependencies { - compile project(modulePrefix + 'library-core') - compile 'com.android.support:support-annotations:' + supportLibraryVersion - androidTestCompile project(modulePrefix + 'testutils') - androidTestCompile 'com.google.dexmaker:dexmaker:' + dexmakerVersion - androidTestCompile 'com.google.dexmaker:dexmaker-mockito:' + dexmakerVersion - androidTestCompile 'org.mockito:mockito-core:' + mockitoVersion + implementation 'com.android.support:support-annotations:' + supportLibraryVersion + implementation project(modulePrefix + 'library-core') + testImplementation project(modulePrefix + 'testutils-robolectric') } ext { diff --git a/library/hls/src/androidTest/java/com/google/android/exoplayer2/source/hls/offline/HlsDownloadTestData.java b/library/hls/src/androidTest/java/com/google/android/exoplayer2/source/hls/offline/HlsDownloadTestData.java deleted file mode 100644 index ec70fb1200..0000000000 --- a/library/hls/src/androidTest/java/com/google/android/exoplayer2/source/hls/offline/HlsDownloadTestData.java +++ /dev/null @@ -1,77 +0,0 @@ -/* - * Copyright (C) 2017 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.exoplayer2.source.hls.offline; - -/** - * Data for HLS downloading tests. - */ -/* package */ interface HlsDownloadTestData { - - String MASTER_PLAYLIST_URI = "test.m3u8"; - - String MEDIA_PLAYLIST_0_DIR = "gear0/"; - String MEDIA_PLAYLIST_0_URI = MEDIA_PLAYLIST_0_DIR + "prog_index.m3u8"; - String MEDIA_PLAYLIST_1_DIR = "gear1/"; - String MEDIA_PLAYLIST_1_URI = MEDIA_PLAYLIST_1_DIR + "prog_index.m3u8"; - String MEDIA_PLAYLIST_2_DIR = "gear2/"; - String MEDIA_PLAYLIST_2_URI = MEDIA_PLAYLIST_2_DIR + "prog_index.m3u8"; - String MEDIA_PLAYLIST_3_DIR = "gear3/"; - String MEDIA_PLAYLIST_3_URI = MEDIA_PLAYLIST_3_DIR + "prog_index.m3u8"; - - byte[] MASTER_PLAYLIST_DATA = - ("#EXTM3U\n" - + "#EXT-X-STREAM-INF:BANDWIDTH=232370,CODECS=\"mp4a.40.2, avc1.4d4015\"\n" - + MEDIA_PLAYLIST_1_URI + "\n" - + "#EXT-X-STREAM-INF:BANDWIDTH=649879,CODECS=\"mp4a.40.2, avc1.4d401e\"\n" - + MEDIA_PLAYLIST_2_URI + "\n" - + "#EXT-X-STREAM-INF:BANDWIDTH=991714,CODECS=\"mp4a.40.2, avc1.4d401e\"\n" - + MEDIA_PLAYLIST_3_URI + "\n" - + "#EXT-X-STREAM-INF:BANDWIDTH=41457,CODECS=\"mp4a.40.2\"\n" - + MEDIA_PLAYLIST_0_URI).getBytes(); - - byte[] MEDIA_PLAYLIST_DATA = - ("#EXTM3U\n" - + "#EXT-X-TARGETDURATION:10\n" - + "#EXT-X-VERSION:3\n" - + "#EXT-X-MEDIA-SEQUENCE:0\n" - + "#EXT-X-PLAYLIST-TYPE:VOD\n" - + "#EXTINF:9.97667,\n" - + "fileSequence0.ts\n" - + "#EXTINF:9.97667,\n" - + "fileSequence1.ts\n" - + "#EXTINF:9.97667,\n" - + "fileSequence2.ts\n" - + "#EXT-X-ENDLIST").getBytes(); - - String ENC_MEDIA_PLAYLIST_URI = "enc_index.m3u8"; - - byte[] ENC_MEDIA_PLAYLIST_DATA = - ("#EXTM3U\n" - + "#EXT-X-TARGETDURATION:10\n" - + "#EXT-X-VERSION:3\n" - + "#EXT-X-MEDIA-SEQUENCE:0\n" - + "#EXT-X-PLAYLIST-TYPE:VOD\n" - + "#EXT-X-KEY:METHOD=AES-128,URI=\"enc.key\"\n" - + "#EXTINF:9.97667,\n" - + "fileSequence0.ts\n" - + "#EXTINF:9.97667,\n" - + "fileSequence1.ts\n" - + "#EXT-X-KEY:METHOD=AES-128,URI=\"enc2.key\"\n" - + "#EXTINF:9.97667,\n" - + "fileSequence2.ts\n" - + "#EXT-X-ENDLIST").getBytes(); - -} diff --git a/library/hls/src/main/java/com/google/android/exoplayer2/source/hls/HlsChunkSource.java b/library/hls/src/main/java/com/google/android/exoplayer2/source/hls/HlsChunkSource.java index db0db47aee..d8496a63d2 100644 --- a/library/hls/src/main/java/com/google/android/exoplayer2/source/hls/HlsChunkSource.java +++ b/library/hls/src/main/java/com/google/android/exoplayer2/source/hls/HlsChunkSource.java @@ -261,9 +261,13 @@ import java.util.List; // If the playlist is too old to contain the chunk, we need to refresh it. chunkMediaSequence = mediaPlaylist.mediaSequence + mediaPlaylist.segments.size(); } else { - chunkMediaSequence = Util.binarySearchFloor(mediaPlaylist.segments, - targetPositionUs - mediaPlaylist.startTimeUs, true, - !playlistTracker.isLive() || previous == null) + mediaPlaylist.mediaSequence; + chunkMediaSequence = + Util.binarySearchFloor( + mediaPlaylist.segments, + targetPositionUs, + /* inclusive= */ true, + /* stayInBounds= */ !playlistTracker.isLive() || previous == null) + + mediaPlaylist.mediaSequence; if (chunkMediaSequence < mediaPlaylist.mediaSequence && previous != null) { // We try getting the next chunk without adapting in case that's the reason for falling // behind the live window. @@ -320,7 +324,9 @@ import java.util.List; } // Compute start time of the next chunk. - long startTimeUs = mediaPlaylist.startTimeUs + segment.relativeStartTimeUs; + long offsetFromInitialStartTimeUs = + mediaPlaylist.startTimeUs - playlistTracker.getInitialStartTimeUs(); + long startTimeUs = offsetFromInitialStartTimeUs + segment.relativeStartTimeUs; int discontinuitySequence = mediaPlaylist.discontinuitySequence + segment.relativeDiscontinuitySequence; TimestampAdjuster timestampAdjuster = timestampAdjusterProvider.getAdjuster( diff --git a/library/hls/src/main/java/com/google/android/exoplayer2/source/hls/HlsMediaSource.java b/library/hls/src/main/java/com/google/android/exoplayer2/source/hls/HlsMediaSource.java index 5113bef6e0..1fe0d72ea1 100644 --- a/library/hls/src/main/java/com/google/android/exoplayer2/source/hls/HlsMediaSource.java +++ b/library/hls/src/main/java/com/google/android/exoplayer2/source/hls/HlsMediaSource.java @@ -366,28 +366,50 @@ public final class HlsMediaSource implements MediaSource, @Override public void onPrimaryPlaylistRefreshed(HlsMediaPlaylist playlist) { SinglePeriodTimeline timeline; - long presentationStartTimeMs = playlist.hasProgramDateTime ? 0 : C.TIME_UNSET; long windowStartTimeMs = playlist.hasProgramDateTime ? C.usToMs(playlist.startTimeUs) : C.TIME_UNSET; + // For playlist types EVENT and VOD we know segments are never removed, so the presentation + // started at the same time as the window. Otherwise, we don't know the presentation start time. + long presentationStartTimeMs = + playlist.playlistType == HlsMediaPlaylist.PLAYLIST_TYPE_EVENT + || playlist.playlistType == HlsMediaPlaylist.PLAYLIST_TYPE_VOD + ? windowStartTimeMs + : C.TIME_UNSET; long windowDefaultStartPositionUs = playlist.startOffsetUs; if (playlistTracker.isLive()) { - long periodDurationUs = playlist.hasEndTag ? (playlist.startTimeUs + playlist.durationUs) - : C.TIME_UNSET; + long offsetFromInitialStartTimeUs = + playlist.startTimeUs - playlistTracker.getInitialStartTimeUs(); + long periodDurationUs = + playlist.hasEndTag ? offsetFromInitialStartTimeUs + playlist.durationUs : C.TIME_UNSET; List segments = playlist.segments; if (windowDefaultStartPositionUs == C.TIME_UNSET) { windowDefaultStartPositionUs = segments.isEmpty() ? 0 : segments.get(Math.max(0, segments.size() - 3)).relativeStartTimeUs; } - timeline = new SinglePeriodTimeline(presentationStartTimeMs, windowStartTimeMs, - periodDurationUs, playlist.durationUs, playlist.startTimeUs, windowDefaultStartPositionUs, - true, !playlist.hasEndTag); + timeline = + new SinglePeriodTimeline( + presentationStartTimeMs, + windowStartTimeMs, + periodDurationUs, + /* windowDurationUs= */ playlist.durationUs, + /* windowPositionInPeriodUs= */ offsetFromInitialStartTimeUs, + windowDefaultStartPositionUs, + /* isSeekable= */ true, + /* isDynamic= */ !playlist.hasEndTag); } else /* not live */ { if (windowDefaultStartPositionUs == C.TIME_UNSET) { windowDefaultStartPositionUs = 0; } - timeline = new SinglePeriodTimeline(presentationStartTimeMs, windowStartTimeMs, - playlist.startTimeUs + playlist.durationUs, playlist.durationUs, playlist.startTimeUs, - windowDefaultStartPositionUs, true, false); + timeline = + new SinglePeriodTimeline( + presentationStartTimeMs, + windowStartTimeMs, + /* periodDurationUs= */ playlist.durationUs, + /* windowDurationUs= */ playlist.durationUs, + /* windowPositionInPeriodUs= */ 0, + windowDefaultStartPositionUs, + /* isSeekable= */ true, + /* isDynamic= */ false); } sourceListener.onSourceInfoRefreshed(this, timeline, new HlsManifest(playlistTracker.getMasterPlaylist(), playlist)); diff --git a/library/hls/src/main/java/com/google/android/exoplayer2/source/hls/playlist/HlsPlaylistTracker.java b/library/hls/src/main/java/com/google/android/exoplayer2/source/hls/playlist/HlsPlaylistTracker.java index d2f16b5c27..2e565c322a 100644 --- a/library/hls/src/main/java/com/google/android/exoplayer2/source/hls/playlist/HlsPlaylistTracker.java +++ b/library/hls/src/main/java/com/google/android/exoplayer2/source/hls/playlist/HlsPlaylistTracker.java @@ -83,7 +83,6 @@ public final class HlsPlaylistTracker implements Loader.Callback(); playlistRefreshHandler = new Handler(); + initialStartTimeUs = C.TIME_UNSET; } /** @@ -208,6 +209,11 @@ public final class HlsPlaylistTracker implements Loader.Callback - - - - - - - + diff --git a/library/hls/src/test/java/com/google/android/exoplayer2/source/hls/offline/HlsDownloadTestData.java b/library/hls/src/test/java/com/google/android/exoplayer2/source/hls/offline/HlsDownloadTestData.java new file mode 100644 index 0000000000..55db28a59a --- /dev/null +++ b/library/hls/src/test/java/com/google/android/exoplayer2/source/hls/offline/HlsDownloadTestData.java @@ -0,0 +1,83 @@ +/* + * Copyright (C) 2017 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.exoplayer2.source.hls.offline; + +import com.google.android.exoplayer2.C; +import java.nio.charset.Charset; + +/** Data for HLS downloading tests. */ +/* package */ interface HlsDownloadTestData { + + String MASTER_PLAYLIST_URI = "test.m3u8"; + + String MEDIA_PLAYLIST_0_DIR = "gear0/"; + String MEDIA_PLAYLIST_0_URI = MEDIA_PLAYLIST_0_DIR + "prog_index.m3u8"; + String MEDIA_PLAYLIST_1_DIR = "gear1/"; + String MEDIA_PLAYLIST_1_URI = MEDIA_PLAYLIST_1_DIR + "prog_index.m3u8"; + String MEDIA_PLAYLIST_2_DIR = "gear2/"; + String MEDIA_PLAYLIST_2_URI = MEDIA_PLAYLIST_2_DIR + "prog_index.m3u8"; + String MEDIA_PLAYLIST_3_DIR = "gear3/"; + String MEDIA_PLAYLIST_3_URI = MEDIA_PLAYLIST_3_DIR + "prog_index.m3u8"; + + byte[] MASTER_PLAYLIST_DATA = + ("#EXTM3U\n" + + "#EXT-X-STREAM-INF:BANDWIDTH=232370,CODECS=\"mp4a.40.2, avc1.4d4015\"\n" + + MEDIA_PLAYLIST_1_URI + + "\n" + + "#EXT-X-STREAM-INF:BANDWIDTH=649879,CODECS=\"mp4a.40.2, avc1.4d401e\"\n" + + MEDIA_PLAYLIST_2_URI + + "\n" + + "#EXT-X-STREAM-INF:BANDWIDTH=991714,CODECS=\"mp4a.40.2, avc1.4d401e\"\n" + + MEDIA_PLAYLIST_3_URI + + "\n" + + "#EXT-X-STREAM-INF:BANDWIDTH=41457,CODECS=\"mp4a.40.2\"\n" + + MEDIA_PLAYLIST_0_URI) + .getBytes(Charset.forName(C.UTF8_NAME)); + + byte[] MEDIA_PLAYLIST_DATA = + ("#EXTM3U\n" + + "#EXT-X-TARGETDURATION:10\n" + + "#EXT-X-VERSION:3\n" + + "#EXT-X-MEDIA-SEQUENCE:0\n" + + "#EXT-X-PLAYLIST-TYPE:VOD\n" + + "#EXTINF:9.97667,\n" + + "fileSequence0.ts\n" + + "#EXTINF:9.97667,\n" + + "fileSequence1.ts\n" + + "#EXTINF:9.97667,\n" + + "fileSequence2.ts\n" + + "#EXT-X-ENDLIST") + .getBytes(Charset.forName(C.UTF8_NAME)); + + String ENC_MEDIA_PLAYLIST_URI = "enc_index.m3u8"; + + byte[] ENC_MEDIA_PLAYLIST_DATA = + ("#EXTM3U\n" + + "#EXT-X-TARGETDURATION:10\n" + + "#EXT-X-VERSION:3\n" + + "#EXT-X-MEDIA-SEQUENCE:0\n" + + "#EXT-X-PLAYLIST-TYPE:VOD\n" + + "#EXT-X-KEY:METHOD=AES-128,URI=\"enc.key\"\n" + + "#EXTINF:9.97667,\n" + + "fileSequence0.ts\n" + + "#EXTINF:9.97667,\n" + + "fileSequence1.ts\n" + + "#EXT-X-KEY:METHOD=AES-128,URI=\"enc2.key\"\n" + + "#EXTINF:9.97667,\n" + + "fileSequence2.ts\n" + + "#EXT-X-ENDLIST") + .getBytes(Charset.forName(C.UTF8_NAME)); +} diff --git a/library/hls/src/androidTest/java/com/google/android/exoplayer2/source/hls/offline/HlsDownloaderTest.java b/library/hls/src/test/java/com/google/android/exoplayer2/source/hls/offline/HlsDownloaderTest.java similarity index 78% rename from library/hls/src/androidTest/java/com/google/android/exoplayer2/source/hls/offline/HlsDownloaderTest.java rename to library/hls/src/test/java/com/google/android/exoplayer2/source/hls/offline/HlsDownloaderTest.java index c068a8182b..1e6b98092b 100644 --- a/library/hls/src/androidTest/java/com/google/android/exoplayer2/source/hls/offline/HlsDownloaderTest.java +++ b/library/hls/src/test/java/com/google/android/exoplayer2/source/hls/offline/HlsDownloaderTest.java @@ -33,7 +33,6 @@ import static com.google.android.exoplayer2.testutil.CacheAsserts.assertCachedDa import static com.google.common.truth.Truth.assertThat; import android.net.Uri; -import android.test.InstrumentationTestCase; import com.google.android.exoplayer2.offline.DownloaderConstructorHelper; import com.google.android.exoplayer2.source.hls.playlist.HlsMasterPlaylist; import com.google.android.exoplayer2.testutil.FakeDataSet; @@ -42,40 +41,47 @@ import com.google.android.exoplayer2.upstream.cache.NoOpCacheEvictor; import com.google.android.exoplayer2.upstream.cache.SimpleCache; import com.google.android.exoplayer2.util.Util; import java.io.File; +import org.junit.After; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.robolectric.RobolectricTestRunner; +import org.robolectric.RuntimeEnvironment; /** Unit tests for {@link HlsDownloader}. */ -public class HlsDownloaderTest extends InstrumentationTestCase { +@RunWith(RobolectricTestRunner.class) +public class HlsDownloaderTest { private SimpleCache cache; private File tempFolder; private FakeDataSet fakeDataSet; private HlsDownloader hlsDownloader; - @Override + @Before public void setUp() throws Exception { - super.setUp(); - tempFolder = Util.createTempDirectory(getInstrumentation().getContext(), "ExoPlayerTest"); + tempFolder = Util.createTempDirectory(RuntimeEnvironment.application, "ExoPlayerTest"); cache = new SimpleCache(tempFolder, new NoOpCacheEvictor()); - fakeDataSet = new FakeDataSet() - .setData(MASTER_PLAYLIST_URI, MASTER_PLAYLIST_DATA) - .setData(MEDIA_PLAYLIST_1_URI, MEDIA_PLAYLIST_DATA) - .setRandomData(MEDIA_PLAYLIST_1_DIR + "fileSequence0.ts", 10) - .setRandomData(MEDIA_PLAYLIST_1_DIR + "fileSequence1.ts", 11) - .setRandomData(MEDIA_PLAYLIST_1_DIR + "fileSequence2.ts", 12) - .setData(MEDIA_PLAYLIST_2_URI, MEDIA_PLAYLIST_DATA) - .setRandomData(MEDIA_PLAYLIST_2_DIR + "fileSequence0.ts", 13) - .setRandomData(MEDIA_PLAYLIST_2_DIR + "fileSequence1.ts", 14) - .setRandomData(MEDIA_PLAYLIST_2_DIR + "fileSequence2.ts", 15); + fakeDataSet = + new FakeDataSet() + .setData(MASTER_PLAYLIST_URI, MASTER_PLAYLIST_DATA) + .setData(MEDIA_PLAYLIST_1_URI, MEDIA_PLAYLIST_DATA) + .setRandomData(MEDIA_PLAYLIST_1_DIR + "fileSequence0.ts", 10) + .setRandomData(MEDIA_PLAYLIST_1_DIR + "fileSequence1.ts", 11) + .setRandomData(MEDIA_PLAYLIST_1_DIR + "fileSequence2.ts", 12) + .setData(MEDIA_PLAYLIST_2_URI, MEDIA_PLAYLIST_DATA) + .setRandomData(MEDIA_PLAYLIST_2_DIR + "fileSequence0.ts", 13) + .setRandomData(MEDIA_PLAYLIST_2_DIR + "fileSequence1.ts", 14) + .setRandomData(MEDIA_PLAYLIST_2_DIR + "fileSequence2.ts", 15); hlsDownloader = getHlsDownloader(MASTER_PLAYLIST_URI); } - @Override + @After public void tearDown() throws Exception { Util.recursiveDelete(tempFolder); - super.tearDown(); } + @Test public void testDownloadManifest() throws Exception { HlsMasterPlaylist manifest = hlsDownloader.getManifest(); @@ -83,17 +89,23 @@ public class HlsDownloaderTest extends InstrumentationTestCase { assertCachedData(cache, fakeDataSet, MASTER_PLAYLIST_URI); } + @Test public void testSelectRepresentationsClearsPreviousSelection() throws Exception { hlsDownloader.selectRepresentations(new String[] {MEDIA_PLAYLIST_1_URI}); hlsDownloader.selectRepresentations(new String[] {MEDIA_PLAYLIST_2_URI}); hlsDownloader.download(null); - assertCachedData(cache, fakeDataSet, MASTER_PLAYLIST_URI, MEDIA_PLAYLIST_2_URI, + assertCachedData( + cache, + fakeDataSet, + MASTER_PLAYLIST_URI, + MEDIA_PLAYLIST_2_URI, MEDIA_PLAYLIST_2_DIR + "fileSequence0.ts", MEDIA_PLAYLIST_2_DIR + "fileSequence1.ts", MEDIA_PLAYLIST_2_DIR + "fileSequence2.ts"); } + @Test public void testCounterMethods() throws Exception { hlsDownloader.selectRepresentations(new String[] {MEDIA_PLAYLIST_1_URI}); hlsDownloader.download(null); @@ -104,12 +116,12 @@ public class HlsDownloaderTest extends InstrumentationTestCase { .isEqualTo(MEDIA_PLAYLIST_DATA.length + 10 + 11 + 12); } + @Test public void testInitStatus() throws Exception { hlsDownloader.selectRepresentations(new String[] {MEDIA_PLAYLIST_1_URI}); hlsDownloader.download(null); - HlsDownloader newHlsDownloader = - getHlsDownloader(MASTER_PLAYLIST_URI); + HlsDownloader newHlsDownloader = getHlsDownloader(MASTER_PLAYLIST_URI); newHlsDownloader.selectRepresentations(new String[] {MEDIA_PLAYLIST_1_URI}); newHlsDownloader.init(); @@ -119,16 +131,22 @@ public class HlsDownloaderTest extends InstrumentationTestCase { .isEqualTo(MEDIA_PLAYLIST_DATA.length + 10 + 11 + 12); } + @Test public void testDownloadRepresentation() throws Exception { hlsDownloader.selectRepresentations(new String[] {MEDIA_PLAYLIST_1_URI}); hlsDownloader.download(null); - assertCachedData(cache, fakeDataSet, MASTER_PLAYLIST_URI, MEDIA_PLAYLIST_1_URI, + assertCachedData( + cache, + fakeDataSet, + MASTER_PLAYLIST_URI, + MEDIA_PLAYLIST_1_URI, MEDIA_PLAYLIST_1_DIR + "fileSequence0.ts", MEDIA_PLAYLIST_1_DIR + "fileSequence1.ts", MEDIA_PLAYLIST_1_DIR + "fileSequence2.ts"); } + @Test public void testDownloadMultipleRepresentations() throws Exception { hlsDownloader.selectRepresentations(new String[] {MEDIA_PLAYLIST_1_URI, MEDIA_PLAYLIST_2_URI}); hlsDownloader.download(null); @@ -136,9 +154,11 @@ public class HlsDownloaderTest extends InstrumentationTestCase { assertCachedData(cache, fakeDataSet); } + @Test public void testDownloadAllRepresentations() throws Exception { // Add data for the rest of the playlists - fakeDataSet.setData(MEDIA_PLAYLIST_0_URI, MEDIA_PLAYLIST_DATA) + fakeDataSet + .setData(MEDIA_PLAYLIST_0_URI, MEDIA_PLAYLIST_DATA) .setRandomData(MEDIA_PLAYLIST_0_DIR + "fileSequence0.ts", 10) .setRandomData(MEDIA_PLAYLIST_0_DIR + "fileSequence1.ts", 11) .setRandomData(MEDIA_PLAYLIST_0_DIR + "fileSequence2.ts", 12) @@ -167,6 +187,7 @@ public class HlsDownloaderTest extends InstrumentationTestCase { hlsDownloader.remove(); } + @Test public void testRemoveAll() throws Exception { hlsDownloader.selectRepresentations(new String[] {MEDIA_PLAYLIST_1_URI, MEDIA_PLAYLIST_2_URI}); hlsDownloader.download(null); @@ -175,27 +196,32 @@ public class HlsDownloaderTest extends InstrumentationTestCase { assertCacheEmpty(cache); } + @Test public void testDownloadMediaPlaylist() throws Exception { hlsDownloader = getHlsDownloader(MEDIA_PLAYLIST_1_URI); hlsDownloader.selectRepresentations(new String[] {MEDIA_PLAYLIST_1_URI}); hlsDownloader.download(null); - assertCachedData(cache, fakeDataSet, MEDIA_PLAYLIST_1_URI, + assertCachedData( + cache, + fakeDataSet, + MEDIA_PLAYLIST_1_URI, MEDIA_PLAYLIST_1_DIR + "fileSequence0.ts", MEDIA_PLAYLIST_1_DIR + "fileSequence1.ts", MEDIA_PLAYLIST_1_DIR + "fileSequence2.ts"); } + @Test public void testDownloadEncMediaPlaylist() throws Exception { - fakeDataSet = new FakeDataSet() - .setData(ENC_MEDIA_PLAYLIST_URI, ENC_MEDIA_PLAYLIST_DATA) - .setRandomData("enc.key", 8) - .setRandomData("enc2.key", 9) - .setRandomData("fileSequence0.ts", 10) - .setRandomData("fileSequence1.ts", 11) - .setRandomData("fileSequence2.ts", 12); - hlsDownloader = - getHlsDownloader(ENC_MEDIA_PLAYLIST_URI); + fakeDataSet = + new FakeDataSet() + .setData(ENC_MEDIA_PLAYLIST_URI, ENC_MEDIA_PLAYLIST_DATA) + .setRandomData("enc.key", 8) + .setRandomData("enc2.key", 9) + .setRandomData("fileSequence0.ts", 10) + .setRandomData("fileSequence1.ts", 11) + .setRandomData("fileSequence2.ts", 12); + hlsDownloader = getHlsDownloader(ENC_MEDIA_PLAYLIST_URI); hlsDownloader.selectRepresentations(new String[] {ENC_MEDIA_PLAYLIST_URI}); hlsDownloader.download(null); @@ -204,8 +230,7 @@ public class HlsDownloaderTest extends InstrumentationTestCase { private HlsDownloader getHlsDownloader(String mediaPlaylistUri) { Factory factory = new Factory(null).setFakeDataSet(fakeDataSet); - return new HlsDownloader(Uri.parse(mediaPlaylistUri), - new DownloaderConstructorHelper(cache, factory)); + return new HlsDownloader( + Uri.parse(mediaPlaylistUri), new DownloaderConstructorHelper(cache, factory)); } - } diff --git a/library/hls/src/androidTest/java/com/google/android/exoplayer2/source/hls/playlist/HlsMasterPlaylistParserTest.java b/library/hls/src/test/java/com/google/android/exoplayer2/source/hls/playlist/HlsMasterPlaylistParserTest.java similarity index 59% rename from library/hls/src/androidTest/java/com/google/android/exoplayer2/source/hls/playlist/HlsMasterPlaylistParserTest.java rename to library/hls/src/test/java/com/google/android/exoplayer2/source/hls/playlist/HlsMasterPlaylistParserTest.java index 2944d01b57..86426e1f94 100644 --- a/library/hls/src/androidTest/java/com/google/android/exoplayer2/source/hls/playlist/HlsMasterPlaylistParserTest.java +++ b/library/hls/src/test/java/com/google/android/exoplayer2/source/hls/playlist/HlsMasterPlaylistParserTest.java @@ -16,6 +16,7 @@ package com.google.android.exoplayer2.source.hls.playlist; import static com.google.common.truth.Truth.assertThat; +import static org.junit.Assert.fail; import android.net.Uri; import com.google.android.exoplayer2.C; @@ -26,70 +27,85 @@ import java.io.ByteArrayInputStream; import java.io.IOException; import java.nio.charset.Charset; import java.util.List; -import junit.framework.TestCase; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.robolectric.RobolectricTestRunner; -/** - * Test for {@link HlsMasterPlaylistParserTest}. - */ -public class HlsMasterPlaylistParserTest extends TestCase { +/** Test for {@link HlsMasterPlaylistParserTest}. */ +@RunWith(RobolectricTestRunner.class) +public class HlsMasterPlaylistParserTest { private static final String PLAYLIST_URI = "https://example.com/test.m3u8"; - private static final String PLAYLIST_SIMPLE = " #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,FRAME-RATE=25,RESOLUTION=384x160\n" - + "http://example.com/mid.m3u8\n" - + "\n" - + "#EXT-X-STREAM-INF:BANDWIDTH=7680000,FRAME-RATE=29.997\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"; + private static final String PLAYLIST_SIMPLE = + " #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,FRAME-RATE=25,RESOLUTION=384x160\n" + + "http://example.com/mid.m3u8\n" + + "\n" + + "#EXT-X-STREAM-INF:BANDWIDTH=7680000,FRAME-RATE=29.997\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"; - private static final String PLAYLIST_WITH_AVG_BANDWIDTH = " #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,AVERAGE-BANDWIDTH=1270000," - + "CODECS=\"mp4a.40.2 , avc1.66.30 \"\n" - + "http://example.com/spaces_in_codecs.m3u8\n"; + private static final String PLAYLIST_WITH_AVG_BANDWIDTH = + " #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,AVERAGE-BANDWIDTH=1270000," + + "CODECS=\"mp4a.40.2 , avc1.66.30 \"\n" + + "http://example.com/spaces_in_codecs.m3u8\n"; - private static final String PLAYLIST_WITH_INVALID_HEADER = "#EXTMU3\n" - + "#EXT-X-STREAM-INF:BANDWIDTH=1280000,CODECS=\"mp4a.40.2,avc1.66.30\",RESOLUTION=304x128\n" - + "http://example.com/low.m3u8\n"; + private static final String PLAYLIST_WITH_INVALID_HEADER = + "#EXTMU3\n" + + "#EXT-X-STREAM-INF:BANDWIDTH=1280000," + + "CODECS=\"mp4a.40.2,avc1.66.30\",RESOLUTION=304x128\n" + + "http://example.com/low.m3u8\n"; - private static final String PLAYLIST_WITH_CC = " #EXTM3U \n" - + "#EXT-X-MEDIA:TYPE=CLOSED-CAPTIONS,LANGUAGE=\"es\",NAME=\"Eng\",INSTREAM-ID=\"SERVICE4\"\n" - + "#EXT-X-STREAM-INF:BANDWIDTH=1280000,CODECS=\"mp4a.40.2,avc1.66.30\",RESOLUTION=304x128\n" - + "http://example.com/low.m3u8\n"; + private static final String PLAYLIST_WITH_CC = + " #EXTM3U \n" + + "#EXT-X-MEDIA:TYPE=CLOSED-CAPTIONS," + + "LANGUAGE=\"es\",NAME=\"Eng\",INSTREAM-ID=\"SERVICE4\"\n" + + "#EXT-X-STREAM-INF:BANDWIDTH=1280000," + + "CODECS=\"mp4a.40.2,avc1.66.30\",RESOLUTION=304x128\n" + + "http://example.com/low.m3u8\n"; - private static final String PLAYLIST_WITHOUT_CC = " #EXTM3U \n" - + "#EXT-X-MEDIA:TYPE=CLOSED-CAPTIONS,LANGUAGE=\"es\",NAME=\"Eng\",INSTREAM-ID=\"SERVICE4\"\n" - + "#EXT-X-STREAM-INF:BANDWIDTH=1280000,CODECS=\"mp4a.40.2,avc1.66.30\",RESOLUTION=304x128," - + "CLOSED-CAPTIONS=NONE\n" - + "http://example.com/low.m3u8\n"; + private static final String PLAYLIST_WITHOUT_CC = + " #EXTM3U \n" + + "#EXT-X-MEDIA:TYPE=CLOSED-CAPTIONS," + + "LANGUAGE=\"es\",NAME=\"Eng\",INSTREAM-ID=\"SERVICE4\"\n" + + "#EXT-X-STREAM-INF:BANDWIDTH=1280000," + + "CODECS=\"mp4a.40.2,avc1.66.30\",RESOLUTION=304x128," + + "CLOSED-CAPTIONS=NONE\n" + + "http://example.com/low.m3u8\n"; - private static final String PLAYLIST_WITH_AUDIO_MEDIA_TAG = "#EXTM3U\n" - + "#EXT-X-STREAM-INF:BANDWIDTH=2227464,CODECS=\"avc1.640020,mp4a.40.2\",AUDIO=\"aud1\"\n" - + "uri1.m3u8\n" - + "#EXT-X-STREAM-INF:BANDWIDTH=8178040,CODECS=\"avc1.64002a,mp4a.40.2\",AUDIO=\"aud1\"\n" - + "uri2.m3u8\n" - + "#EXT-X-STREAM-INF:BANDWIDTH=2448841,CODECS=\"avc1.640020,ac-3\",AUDIO=\"aud2\"\n" - + "uri1.m3u8\n" - + "#EXT-X-STREAM-INF:BANDWIDTH=8399417,CODECS=\"avc1.64002a,ac-3\",AUDIO=\"aud2\"\n" - + "uri2.m3u8\n" - + "#EXT-X-MEDIA:TYPE=AUDIO,GROUP-ID=\"aud1\",LANGUAGE=\"en\",NAME=\"English\"," - + "AUTOSELECT=YES,DEFAULT=YES,CHANNELS=\"2\",URI=\"a1/prog_index.m3u8\"\n" - + "#EXT-X-MEDIA:TYPE=AUDIO,GROUP-ID=\"aud2\",LANGUAGE=\"en\",NAME=\"English\"," - + "AUTOSELECT=YES,DEFAULT=YES,CHANNELS=\"6\",URI=\"a2/prog_index.m3u8\"\n"; + private static final String PLAYLIST_WITH_AUDIO_MEDIA_TAG = + "#EXTM3U\n" + + "#EXT-X-STREAM-INF:BANDWIDTH=2227464,CODECS=\"avc1.640020,mp4a.40.2\",AUDIO=\"aud1\"\n" + + "uri1.m3u8\n" + + "#EXT-X-STREAM-INF:BANDWIDTH=8178040,CODECS=\"avc1.64002a,mp4a.40.2\",AUDIO=\"aud1\"\n" + + "uri2.m3u8\n" + + "#EXT-X-STREAM-INF:BANDWIDTH=2448841,CODECS=\"avc1.640020,ac-3\",AUDIO=\"aud2\"\n" + + "uri1.m3u8\n" + + "#EXT-X-STREAM-INF:BANDWIDTH=8399417,CODECS=\"avc1.64002a,ac-3\",AUDIO=\"aud2\"\n" + + "uri2.m3u8\n" + + "#EXT-X-MEDIA:TYPE=AUDIO,GROUP-ID=\"aud1\",LANGUAGE=\"en\",NAME=\"English\"," + + "AUTOSELECT=YES,DEFAULT=YES,CHANNELS=\"2\",URI=\"a1/prog_index.m3u8\"\n" + + "#EXT-X-MEDIA:TYPE=AUDIO,GROUP-ID=\"aud2\",LANGUAGE=\"en\",NAME=\"English\"," + + "AUTOSELECT=YES,DEFAULT=YES,CHANNELS=\"6\",URI=\"a2/prog_index.m3u8\"\n"; + @Test public void testParseMasterPlaylist() throws IOException { HlsMasterPlaylist masterPlaylist = parseMasterPlaylist(PLAYLIST_URI, PLAYLIST_SIMPLE); @@ -129,9 +145,10 @@ public class HlsMasterPlaylistParserTest extends TestCase { assertThat(variants.get(4).url).isEqualTo("http://example.com/audio-only.m3u8"); } + @Test public void testMasterPlaylistWithBandwdithAverage() throws IOException { - HlsMasterPlaylist masterPlaylist = parseMasterPlaylist(PLAYLIST_URI, - PLAYLIST_WITH_AVG_BANDWIDTH); + HlsMasterPlaylist masterPlaylist = + parseMasterPlaylist(PLAYLIST_URI, PLAYLIST_WITH_AVG_BANDWIDTH); List variants = masterPlaylist.variants; @@ -139,6 +156,7 @@ public class HlsMasterPlaylistParserTest extends TestCase { assertThat(variants.get(1).format.bitrate).isEqualTo(1270000); } + @Test public void testPlaylistWithInvalidHeader() throws IOException { try { parseMasterPlaylist(PLAYLIST_URI, PLAYLIST_WITH_INVALID_HEADER); @@ -148,6 +166,7 @@ public class HlsMasterPlaylistParserTest extends TestCase { } } + @Test public void testPlaylistWithClosedCaption() throws IOException { HlsMasterPlaylist playlist = parseMasterPlaylist(PLAYLIST_URI, PLAYLIST_WITH_CC); assertThat(playlist.muxedCaptionFormats).hasSize(1); @@ -157,11 +176,13 @@ public class HlsMasterPlaylistParserTest extends TestCase { assertThat(closedCaptionFormat.language).isEqualTo("es"); } + @Test public void testPlaylistWithoutClosedCaptions() throws IOException { HlsMasterPlaylist playlist = parseMasterPlaylist(PLAYLIST_URI, PLAYLIST_WITHOUT_CC); assertThat(playlist.muxedCaptionFormats).isEmpty(); } + @Test public void testCodecPropagation() throws IOException { HlsMasterPlaylist playlist = parseMasterPlaylist(PLAYLIST_URI, PLAYLIST_WITH_AUDIO_MEDIA_TAG); @@ -177,9 +198,8 @@ public class HlsMasterPlaylistParserTest extends TestCase { private static HlsMasterPlaylist parseMasterPlaylist(String uri, String playlistString) throws IOException { Uri playlistUri = Uri.parse(uri); - ByteArrayInputStream inputStream = new ByteArrayInputStream( - playlistString.getBytes(Charset.forName(C.UTF8_NAME))); + ByteArrayInputStream inputStream = + new ByteArrayInputStream(playlistString.getBytes(Charset.forName(C.UTF8_NAME))); return (HlsMasterPlaylist) new HlsPlaylistParser().parse(playlistUri, inputStream); } - } diff --git a/library/hls/src/androidTest/java/com/google/android/exoplayer2/source/hls/playlist/HlsMediaPlaylistParserTest.java b/library/hls/src/test/java/com/google/android/exoplayer2/source/hls/playlist/HlsMediaPlaylistParserTest.java similarity index 76% rename from library/hls/src/androidTest/java/com/google/android/exoplayer2/source/hls/playlist/HlsMediaPlaylistParserTest.java rename to library/hls/src/test/java/com/google/android/exoplayer2/source/hls/playlist/HlsMediaPlaylistParserTest.java index 97a5386b04..b10997cfe9 100644 --- a/library/hls/src/androidTest/java/com/google/android/exoplayer2/source/hls/playlist/HlsMediaPlaylistParserTest.java +++ b/library/hls/src/test/java/com/google/android/exoplayer2/source/hls/playlist/HlsMediaPlaylistParserTest.java @@ -26,49 +26,53 @@ import java.io.InputStream; import java.nio.charset.Charset; import java.util.List; import java.util.Locale; -import junit.framework.TestCase; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.robolectric.RobolectricTestRunner; -/** - * Test for {@link HlsMediaPlaylistParserTest}. - */ -public class HlsMediaPlaylistParserTest extends TestCase { +/** Test for {@link HlsMediaPlaylistParserTest}. */ +@RunWith(RobolectricTestRunner.class) +public class HlsMediaPlaylistParserTest { - public void testParseMediaPlaylist() throws IOException { + @Test + public void testParseMediaPlaylist() throws Exception { Uri playlistUri = Uri.parse("https://example.com/test.m3u8"); - String playlistString = "#EXTM3U\n" - + "#EXT-X-VERSION:3\n" - + "#EXT-X-PLAYLIST-TYPE:VOD\n" - + "#EXT-X-START:TIME-OFFSET=-25" - + "#EXT-X-TARGETDURATION:8\n" - + "#EXT-X-MEDIA-SEQUENCE:2679\n" - + "#EXT-X-DISCONTINUITY-SEQUENCE:4\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@2147483648\n" - + "https://priv.example.com/fileSequence2680.ts\n" - + "\n" - + "#EXT-X-KEY:METHOD=NONE\n" - + "#EXTINF:7.941,\n" - + "#EXT-X-BYTERANGE:51501\n" // @2147535149 - + "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" // @2147586650 - + "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))); + String playlistString = + "#EXTM3U\n" + + "#EXT-X-VERSION:3\n" + + "#EXT-X-PLAYLIST-TYPE:VOD\n" + + "#EXT-X-START:TIME-OFFSET=-25" + + "#EXT-X-TARGETDURATION:8\n" + + "#EXT-X-MEDIA-SEQUENCE:2679\n" + + "#EXT-X-DISCONTINUITY-SEQUENCE:4\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@2147483648\n" + + "https://priv.example.com/fileSequence2680.ts\n" + + "\n" + + "#EXT-X-KEY:METHOD=NONE\n" + + "#EXTINF:7.941,\n" + + "#EXT-X-BYTERANGE:51501\n" // @2147535149 + + "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" // @2147586650 + + "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))); HlsPlaylist playlist = new HlsPlaylistParser().parse(playlistUri, inputStream); HlsMediaPlaylist mediaPlaylist = (HlsMediaPlaylist) playlist; @@ -136,6 +140,7 @@ public class HlsMediaPlaylistParserTest extends TestCase { assertThat(segment.url).isEqualTo("https://priv.example.com/fileSequence2683.ts"); } + @Test public void testGapTag() throws IOException { Uri playlistUri = Uri.parse("https://example.com/test2.m3u8"); String playlistString = @@ -170,5 +175,4 @@ public class HlsMediaPlaylistParserTest extends TestCase { assertThat(playlist.segments.get(2).hasGapTag).isTrue(); assertThat(playlist.segments.get(3).hasGapTag).isFalse(); } - } diff --git a/library/hls/src/test/resources/robolectric.properties b/library/hls/src/test/resources/robolectric.properties new file mode 100644 index 0000000000..2f3210368e --- /dev/null +++ b/library/hls/src/test/resources/robolectric.properties @@ -0,0 +1 @@ +manifest=src/test/AndroidManifest.xml diff --git a/library/smoothstreaming/build.gradle b/library/smoothstreaming/build.gradle index ee5a8c4e73..6ca5570a93 100644 --- a/library/smoothstreaming/build.gradle +++ b/library/smoothstreaming/build.gradle @@ -33,12 +33,9 @@ android { } dependencies { - compile project(modulePrefix + 'library-core') - compile 'com.android.support:support-annotations:' + supportLibraryVersion - androidTestCompile project(modulePrefix + 'testutils') - androidTestCompile 'com.google.dexmaker:dexmaker:' + dexmakerVersion - androidTestCompile 'com.google.dexmaker:dexmaker-mockito:' + dexmakerVersion - androidTestCompile 'org.mockito:mockito-core:' + mockitoVersion + implementation project(modulePrefix + 'library-core') + implementation 'com.android.support:support-annotations:' + supportLibraryVersion + testImplementation project(modulePrefix + 'testutils-robolectric') } ext { diff --git a/library/smoothstreaming/src/androidTest/AndroidManifest.xml b/library/smoothstreaming/src/test/AndroidManifest.xml similarity index 70% rename from library/smoothstreaming/src/androidTest/AndroidManifest.xml rename to library/smoothstreaming/src/test/AndroidManifest.xml index 8e1e69509d..1a8f8ee9c4 100644 --- a/library/smoothstreaming/src/androidTest/AndroidManifest.xml +++ b/library/smoothstreaming/src/test/AndroidManifest.xml @@ -18,16 +18,6 @@ xmlns:tools="http://schemas.android.com/tools" package="com.google.android.exoplayer2.source.smoothstreaming.test"> - - - - - - - + diff --git a/library/smoothstreaming/src/androidTest/assets/sample_ismc_1 b/library/smoothstreaming/src/test/assets/sample_ismc_1 similarity index 100% rename from library/smoothstreaming/src/androidTest/assets/sample_ismc_1 rename to library/smoothstreaming/src/test/assets/sample_ismc_1 diff --git a/library/smoothstreaming/src/androidTest/assets/sample_ismc_2 b/library/smoothstreaming/src/test/assets/sample_ismc_2 similarity index 100% rename from library/smoothstreaming/src/androidTest/assets/sample_ismc_2 rename to library/smoothstreaming/src/test/assets/sample_ismc_2 diff --git a/library/smoothstreaming/src/androidTest/java/com/google/android/exoplayer2/source/smoothstreaming/manifest/SsManifestParserTest.java b/library/smoothstreaming/src/test/java/com/google/android/exoplayer2/source/smoothstreaming/manifest/SsManifestParserTest.java similarity index 60% rename from library/smoothstreaming/src/androidTest/java/com/google/android/exoplayer2/source/smoothstreaming/manifest/SsManifestParserTest.java rename to library/smoothstreaming/src/test/java/com/google/android/exoplayer2/source/smoothstreaming/manifest/SsManifestParserTest.java index 4663f014ff..2ce9fec970 100644 --- a/library/smoothstreaming/src/androidTest/java/com/google/android/exoplayer2/source/smoothstreaming/manifest/SsManifestParserTest.java +++ b/library/smoothstreaming/src/test/java/com/google/android/exoplayer2/source/smoothstreaming/manifest/SsManifestParserTest.java @@ -16,27 +16,29 @@ package com.google.android.exoplayer2.source.smoothstreaming.manifest; import android.net.Uri; -import android.test.InstrumentationTestCase; import com.google.android.exoplayer2.testutil.TestUtil; import java.io.IOException; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.robolectric.RobolectricTestRunner; +import org.robolectric.RuntimeEnvironment; -/** - * Unit tests for {@link SsManifestParser}. - */ -public final class SsManifestParserTest extends InstrumentationTestCase { +/** Unit tests for {@link SsManifestParser}. */ +@RunWith(RobolectricTestRunner.class) +public final class SsManifestParserTest { private static final String SAMPLE_ISMC_1 = "sample_ismc_1"; private static final String SAMPLE_ISMC_2 = "sample_ismc_2"; - /** - * Simple test to ensure the sample manifests parse without any exceptions being thrown. - */ + /** Simple test to ensure the sample manifests parse without any exceptions being thrown. */ + @Test public void testParseSmoothStreamingManifest() throws IOException { SsManifestParser parser = new SsManifestParser(); - parser.parse(Uri.parse("https://example.com/test.ismc"), - TestUtil.getInputStream(getInstrumentation(), SAMPLE_ISMC_1)); - parser.parse(Uri.parse("https://example.com/test.ismc"), - TestUtil.getInputStream(getInstrumentation(), SAMPLE_ISMC_2)); + parser.parse( + Uri.parse("https://example.com/test.ismc"), + TestUtil.getInputStream(RuntimeEnvironment.application, SAMPLE_ISMC_1)); + parser.parse( + Uri.parse("https://example.com/test.ismc"), + TestUtil.getInputStream(RuntimeEnvironment.application, SAMPLE_ISMC_2)); } - } diff --git a/library/smoothstreaming/src/androidTest/java/com/google/android/exoplayer2/source/smoothstreaming/manifest/SsManifestTest.java b/library/smoothstreaming/src/test/java/com/google/android/exoplayer2/source/smoothstreaming/manifest/SsManifestTest.java similarity index 77% rename from library/smoothstreaming/src/androidTest/java/com/google/android/exoplayer2/source/smoothstreaming/manifest/SsManifestTest.java rename to library/smoothstreaming/src/test/java/com/google/android/exoplayer2/source/smoothstreaming/manifest/SsManifestTest.java index ecf9e77d28..fbb2c3d4c4 100644 --- a/library/smoothstreaming/src/androidTest/java/com/google/android/exoplayer2/source/smoothstreaming/manifest/SsManifestTest.java +++ b/library/smoothstreaming/src/test/java/com/google/android/exoplayer2/source/smoothstreaming/manifest/SsManifestTest.java @@ -26,52 +26,49 @@ import java.util.Arrays; import java.util.Collections; import java.util.List; import java.util.Random; -import junit.framework.TestCase; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.robolectric.RobolectricTestRunner; -/** - * Unit tests for {@link SsManifest}. - */ -public class SsManifestTest extends TestCase { +/** Unit tests for {@link SsManifest}. */ +@RunWith(RobolectricTestRunner.class) +public class SsManifestTest { private static final ProtectionElement DUMMY_PROTECTION_ELEMENT = new ProtectionElement(C.WIDEVINE_UUID, new byte[] {0, 1, 2}); + @Test public void testCopy() throws Exception { Format[][] formats = newFormats(2, 3); - SsManifest sourceManifest = newSsManifest( - newStreamElement("1",formats[0]), - newStreamElement("2", formats[1])); + SsManifest sourceManifest = + newSsManifest(newStreamElement("1", formats[0]), newStreamElement("2", formats[1])); - List keys = Arrays.asList( - new TrackKey(0, 0), - new TrackKey(0, 2), - new TrackKey(1, 0)); + List keys = Arrays.asList(new TrackKey(0, 0), new TrackKey(0, 2), new TrackKey(1, 0)); // Keys don't need to be in any particular order Collections.shuffle(keys, new Random(0)); SsManifest copyManifest = sourceManifest.copy(keys); - SsManifest expectedManifest = newSsManifest( - newStreamElement("1", formats[0][0], formats[0][2]), - newStreamElement("2", formats[1][0])); + SsManifest expectedManifest = + newSsManifest( + newStreamElement("1", formats[0][0], formats[0][2]), + newStreamElement("2", formats[1][0])); assertManifestEquals(expectedManifest, copyManifest); } + @Test public void testCopyRemoveStreamElement() throws Exception { Format[][] formats = newFormats(2, 3); - SsManifest sourceManifest = newSsManifest( - newStreamElement("1", formats[0]), - newStreamElement("2", formats[1])); + SsManifest sourceManifest = + newSsManifest(newStreamElement("1", formats[0]), newStreamElement("2", formats[1])); - List keys = Arrays.asList( - new TrackKey(1, 0)); + List keys = Arrays.asList(new TrackKey(1, 0)); // Keys don't need to be in any particular order Collections.shuffle(keys, new Random(0)); SsManifest copyManifest = sourceManifest.copy(keys); - SsManifest expectedManifest = newSsManifest( - newStreamElement("2", formats[1][0])); + SsManifest expectedManifest = newSsManifest(newStreamElement("2", formats[1][0])); assertManifestEquals(expectedManifest, copyManifest); } @@ -117,13 +114,25 @@ public class SsManifestTest extends TestCase { } private static StreamElement newStreamElement(String name, Format... formats) { - return new StreamElement("baseUri", "chunkTemplate", C.TRACK_TYPE_VIDEO, "subType", - 1000, name, 1024, 768, 1024, 768, null, formats, Collections.emptyList(), 0); + return new StreamElement( + "baseUri", + "chunkTemplate", + C.TRACK_TYPE_VIDEO, + "subType", + 1000, + name, + 1024, + 768, + 1024, + 768, + null, + formats, + Collections.emptyList(), + 0); } private static Format newFormat(String id) { - return Format.createContainerFormat(id, MimeTypes.VIDEO_MP4, MimeTypes.VIDEO_H264, null, - Format.NO_VALUE, 0, null); + return Format.createContainerFormat( + id, MimeTypes.VIDEO_MP4, MimeTypes.VIDEO_H264, null, Format.NO_VALUE, 0, null); } - } diff --git a/library/smoothstreaming/src/test/resources/robolectric.properties b/library/smoothstreaming/src/test/resources/robolectric.properties new file mode 100644 index 0000000000..2f3210368e --- /dev/null +++ b/library/smoothstreaming/src/test/resources/robolectric.properties @@ -0,0 +1 @@ +manifest=src/test/AndroidManifest.xml diff --git a/library/ui/build.gradle b/library/ui/build.gradle index 89734ed806..9689fcef97 100644 --- a/library/ui/build.gradle +++ b/library/ui/build.gradle @@ -33,8 +33,8 @@ android { } dependencies { - compile project(modulePrefix + 'library-core') - compile 'com.android.support:support-annotations:' + supportLibraryVersion + implementation project(modulePrefix + 'library-core') + implementation 'com.android.support:support-annotations:' + supportLibraryVersion } ext { diff --git a/playbacktests/build.gradle b/playbacktests/build.gradle index 6cd56868f9..d5d524b5a5 100644 --- a/playbacktests/build.gradle +++ b/playbacktests/build.gradle @@ -25,8 +25,8 @@ android { } dependencies { - androidTestCompile project(modulePrefix + 'library-core') - androidTestCompile project(modulePrefix + 'library-dash') - androidTestCompile project(modulePrefix + 'library-hls') - androidTestCompile project(modulePrefix + 'testutils') + androidTestImplementation project(modulePrefix + 'library-core') + androidTestImplementation project(modulePrefix + 'library-dash') + androidTestImplementation project(modulePrefix + 'library-hls') + androidTestImplementation project(modulePrefix + 'testutils') } diff --git a/testutils/build.gradle b/testutils/build.gradle index 7c4b163713..a7f05a2c5e 100644 --- a/testutils/build.gradle +++ b/testutils/build.gradle @@ -32,7 +32,9 @@ android { } dependencies { - compile project(modulePrefix + 'library-core') - compile 'org.mockito:mockito-core:' + mockitoVersion - compile 'com.google.truth:truth:' + truthVersion + api 'org.mockito:mockito-core:' + mockitoVersion + api 'com.google.truth:truth:' + truthVersion + implementation 'com.android.support:support-annotations:' + supportLibraryVersion + implementation project(modulePrefix + 'library-core') + testImplementation project(modulePrefix + 'testutils-robolectric') } diff --git a/testutils/src/main/java/com/google/android/exoplayer2/testutil/Action.java b/testutils/src/main/java/com/google/android/exoplayer2/testutil/Action.java index 1b3639788e..60cf6d278b 100644 --- a/testutils/src/main/java/com/google/android/exoplayer2/testutil/Action.java +++ b/testutils/src/main/java/com/google/android/exoplayer2/testutil/Action.java @@ -447,6 +447,36 @@ public abstract class Action { } + /** Throws a playback exception on the playback thread. */ + public static final class ThrowPlaybackException extends Action { + + private final ExoPlaybackException exception; + + /** + * @param tag A tag to use for logging. + * @param exception The exception to throw. + */ + public ThrowPlaybackException(String tag, ExoPlaybackException exception) { + super(tag, "ThrowPlaybackException:" + exception); + this.exception = exception; + } + + @Override + protected void doActionImpl( + SimpleExoPlayer player, MappingTrackSelector trackSelector, Surface surface) { + player + .createMessage( + new Target() { + @Override + public void handleMessage(int messageType, Object payload) + throws ExoPlaybackException { + throw exception; + } + }) + .send(); + } + } + /** * Schedules a play action to be executed, waits until the player reaches the specified position, * and pauses the player again. diff --git a/testutils/src/main/java/com/google/android/exoplayer2/testutil/ActionSchedule.java b/testutils/src/main/java/com/google/android/exoplayer2/testutil/ActionSchedule.java index 31c440553f..2ea8a50a84 100644 --- a/testutils/src/main/java/com/google/android/exoplayer2/testutil/ActionSchedule.java +++ b/testutils/src/main/java/com/google/android/exoplayer2/testutil/ActionSchedule.java @@ -40,6 +40,7 @@ import com.google.android.exoplayer2.testutil.Action.SetRepeatMode; import com.google.android.exoplayer2.testutil.Action.SetShuffleModeEnabled; import com.google.android.exoplayer2.testutil.Action.SetVideoSurface; import com.google.android.exoplayer2.testutil.Action.Stop; +import com.google.android.exoplayer2.testutil.Action.ThrowPlaybackException; import com.google.android.exoplayer2.testutil.Action.WaitForPlaybackState; import com.google.android.exoplayer2.testutil.Action.WaitForPositionDiscontinuity; import com.google.android.exoplayer2.testutil.Action.WaitForSeekProcessed; @@ -412,6 +413,16 @@ public final class ActionSchedule { return apply(new ExecuteRunnable(tag, runnable)); } + /** + * Schedules to throw a playback exception on the playback thread. + * + * @param exception The exception to throw. + * @return The builder, for convenience. + */ + public Builder throwPlaybackException(ExoPlaybackException exception) { + return apply(new ThrowPlaybackException(tag, exception)); + } + public ActionSchedule build() { CallbackAction callbackAction = new CallbackAction(tag); apply(callbackAction); diff --git a/testutils/src/main/java/com/google/android/exoplayer2/testutil/MockitoUtil.java b/testutils/src/main/java/com/google/android/exoplayer2/testutil/MockitoUtil.java deleted file mode 100644 index 6bd1048bc0..0000000000 --- a/testutils/src/main/java/com/google/android/exoplayer2/testutil/MockitoUtil.java +++ /dev/null @@ -1,54 +0,0 @@ -/* - * Copyright (C) 2017 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.exoplayer2.testutil; - -import android.content.Context; -import android.test.InstrumentationTestCase; -import org.mockito.MockitoAnnotations; - -/** - * Utility for setting up Mockito for instrumentation tests. - */ -public final class MockitoUtil { - - /** - * Sets up Mockito for an instrumentation test. - * - * @param instrumentationTestCase The instrumentation test case class. - */ - public static void setUpMockito(InstrumentationTestCase instrumentationTestCase) { - // Workaround for https://code.google.com/p/dexmaker/issues/detail?id=2. - System.setProperty("dexmaker.dexcache", - instrumentationTestCase.getInstrumentation().getTargetContext().getCacheDir().getPath()); - MockitoAnnotations.initMocks(instrumentationTestCase); - } - - /** - * Sets up Mockito for a JUnit4 test. - * - * @param targetContext The target context. Usually obtained from - * {@code InstrumentationRegistry.getTargetContext()} - * @param testClass The JUnit4 test class. - */ - public static void setUpMockito(Context targetContext, Object testClass) { - // Workaround for https://code.google.com/p/dexmaker/issues/detail?id=2. - System.setProperty("dexmaker.dexcache", targetContext.getCacheDir().getPath()); - MockitoAnnotations.initMocks(testClass); - } - - private MockitoUtil() {} - -} diff --git a/testutils/src/main/java/com/google/android/exoplayer2/testutil/OggTestData.java b/testutils/src/main/java/com/google/android/exoplayer2/testutil/OggTestData.java deleted file mode 100644 index 7cae709438..0000000000 --- a/testutils/src/main/java/com/google/android/exoplayer2/testutil/OggTestData.java +++ /dev/null @@ -1,1073 +0,0 @@ -/* - * Copyright (C) 2016 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.exoplayer2.testutil; - -/** - * Provides ogg/vorbis test data in bytes for unit tests. - */ -public final class OggTestData { - - public static FakeExtractorInput createInput(byte[] data, boolean simulateUnknownLength) { - return new FakeExtractorInput.Builder().setData(data).setSimulateIOErrors(true) - .setSimulateUnknownLength(simulateUnknownLength).setSimulatePartialReads(true).build(); - } - - public static byte[] buildOggHeader(int headerType, long granule, int pageSequenceCounter, - int pageSegmentCount) { - return TestUtil.createByteArray( - 0x4F, 0x67, 0x67, 0x53, // Oggs. - 0x00, // Stream revision. - headerType, - (int) (granule) & 0xFF, - (int) (granule >> 8) & 0xFF, - (int) (granule >> 16) & 0xFF, - (int) (granule >> 24) & 0xFF, - (int) (granule >> 32) & 0xFF, - (int) (granule >> 40) & 0xFF, - (int) (granule >> 48) & 0xFF, - (int) (granule >> 56) & 0xFF, - 0x00, // LSB of data serial number. - 0x10, - 0x00, - 0x00, // MSB of data serial number. - (pageSequenceCounter) & 0xFF, - (pageSequenceCounter >> 8) & 0xFF, - (pageSequenceCounter >> 16) & 0xFF, - (pageSequenceCounter >> 24) & 0xFF, - 0x00, // LSB of page checksum. - 0x00, - 0x10, - 0x00, // MSB of page checksum. - pageSegmentCount); - } - - /** - * Returns the initial two pages of bytes which by spec contain the three vorbis header packets: - * identification, comment and setup header. - */ - public static byte[] getVorbisHeaderPages() { - byte[] data = new byte[VORBIS_HEADER_PAGES.length]; - System.arraycopy(VORBIS_HEADER_PAGES, 0, data, 0, - VORBIS_HEADER_PAGES.length); - return data; - } - - /** - * Returns a valid vorbis identification header in bytes. - */ - public static byte[] getIdentificationHeaderData() { - int idHeaderStart = 28; - int idHeaderLength = 30; - byte[] idHeaderData = new byte[idHeaderLength]; - System.arraycopy(VORBIS_HEADER_PAGES, idHeaderStart, idHeaderData, 0, idHeaderLength); - return idHeaderData; - } - - /** - * Returns a valid vorbis comment header with 3 comments including utf8 chars in bytes. - */ - public static byte[] getCommentHeaderDataUTF8() { - byte[] commentHeaderData = new byte[COMMENT_HEADER_WITH_UTF8.length]; - System.arraycopy(COMMENT_HEADER_WITH_UTF8, 0, commentHeaderData, 0, - COMMENT_HEADER_WITH_UTF8.length); - return commentHeaderData; - } - - /** - * Returns a valid vorbis setup header in bytes. - */ - public static byte[] getSetupHeaderData() { - int setupHeaderStart = 146; - int setupHeaderLength = VORBIS_HEADER_PAGES.length - setupHeaderStart; - byte[] setupHeaderData = new byte[setupHeaderLength]; - System.arraycopy(VORBIS_HEADER_PAGES, setupHeaderStart, setupHeaderData, 0, setupHeaderLength); - return setupHeaderData; - } - - private static final byte[] COMMENT_HEADER_WITH_UTF8 = { - (byte) 0x03, (byte) 0x76, (byte) 0x6f, (byte) 0x72, // 3, v, o, r, - (byte) 0x62, (byte) 0x69, (byte) 0x73, (byte) 0x2b, // b, i, s, . - (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x58, - (byte) 0x69, (byte) 0x70, (byte) 0x68, (byte) 0x2e, - (byte) 0x4f, (byte) 0x72, (byte) 0x67, (byte) 0x20, - (byte) 0x6c, (byte) 0x69, (byte) 0x62, (byte) 0x56, - (byte) 0x6f, (byte) 0x72, (byte) 0x62, (byte) 0x69, - (byte) 0x73, (byte) 0x20, (byte) 0x49, (byte) 0x20, - (byte) 0x32, (byte) 0x30, (byte) 0x31, (byte) 0x32, - (byte) 0x30, (byte) 0x32, (byte) 0x30, (byte) 0x33, - (byte) 0x20, (byte) 0x28, (byte) 0x4f, (byte) 0x6d, - (byte) 0x6e, (byte) 0x69, (byte) 0x70, (byte) 0x72, - (byte) 0x65, (byte) 0x73, (byte) 0x65, (byte) 0x6e, - (byte) 0x74, (byte) 0x29, (byte) 0x03, (byte) 0x00, - (byte) 0x00, (byte) 0x00, (byte) 0x0a, (byte) 0x00, - (byte) 0x00, (byte) 0x00, (byte) 0x41, (byte) 0x4c, - (byte) 0x42, (byte) 0x55, (byte) 0x4d, (byte) 0x3d, - (byte) 0xc3, (byte) 0xa4, (byte) 0xc3, (byte) 0xb6, - (byte) 0x13, (byte) 0x00, (byte) 0x00, (byte) 0x00, - (byte) 0x54, (byte) 0x49, (byte) 0x54, (byte) 0x4c, - (byte) 0x45, (byte) 0x3d, (byte) 0x41, (byte) 0x20, - (byte) 0x73, (byte) 0x61, (byte) 0x6d, (byte) 0x70, - (byte) 0x6c, (byte) 0x65, (byte) 0x20, (byte) 0x73, - (byte) 0x6f, (byte) 0x6e, (byte) 0x67, (byte) 0x0d, - (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x41, - (byte) 0x52, (byte) 0x54, (byte) 0x49, (byte) 0x53, - (byte) 0x54, (byte) 0x3d, (byte) 0x47, (byte) 0x6f, - (byte) 0x6f, (byte) 0x67, (byte) 0x6c, (byte) 0x65, - (byte) 0x01 - }; - - // two OGG pages with 3 packets (id, comment and setup header) - // length: 3743 bytes - private static final byte[] VORBIS_HEADER_PAGES = { /* capture pattern ogg header 1 */ - (byte) 0x4f, (byte) 0x67, (byte) 0x67, (byte) 0x53, // O,g,g,S : start pos 0 - (byte) 0x00, (byte) 0x02, (byte) 0x00, (byte) 0x00, - (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, - (byte) 0x00, (byte) 0x00, (byte) 0x5e, (byte) 0x5f, - (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, - (byte) 0x00, (byte) 0x00, (byte) 0x83, (byte) 0x36, - (byte) 0xe3, (byte) 0x49, (byte) 0x01, (byte) 0x1e, /* capture pattern vorbis id header */ - (byte) 0x01, (byte) 0x76, (byte) 0x6f, (byte) 0x72, // 1,v,o,r : start pos 28 - (byte) 0x62, (byte) 0x69, (byte) 0x73, (byte) 0x00, // b,i,s,. - (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x02, - (byte) 0x22, (byte) 0x56, (byte) 0x00, (byte) 0x00, - (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff, - (byte) 0x6a, (byte) 0x04, (byte) 0x01, (byte) 0x00, - (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff, /* capture pattern ogg header 2 */ - (byte) 0xa9, (byte) 0x01, (byte) 0x4f, (byte) 0x67, // .,.,O,g : start pos 86 - (byte) 0x67, (byte) 0x53, (byte) 0x00, (byte) 0x00, // g,S,.,. - (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, - (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, - (byte) 0x5e, (byte) 0x5f, (byte) 0x00, (byte) 0x00, - (byte) 0x01, (byte) 0x00, (byte) 0x00, (byte) 0x00, - (byte) 0x69, (byte) 0xf8, (byte) 0xeb, (byte) 0xe1, - (byte) 0x10, (byte) 0x2d, (byte) 0xff, (byte) 0xff, - (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff, - (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff, - (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff, /* capture pattern vorbis comment header*/ - (byte) 0x1b, (byte) 0x03, (byte) 0x76, (byte) 0x6f, // .,3,v,o : start pos 101 - (byte) 0x72, (byte) 0x62, (byte) 0x69, (byte) 0x73, // r,b,i,s - (byte) 0x1d, (byte) 0x00, (byte) 0x00, (byte) 0x00, - (byte) 0x58, (byte) 0x69, (byte) 0x70, (byte) 0x68, - (byte) 0x2e, (byte) 0x4f, (byte) 0x72, (byte) 0x67, - (byte) 0x20, (byte) 0x6c, (byte) 0x69, (byte) 0x62, - (byte) 0x56, (byte) 0x6f, (byte) 0x72, (byte) 0x62, - (byte) 0x69, (byte) 0x73, (byte) 0x20, (byte) 0x49, - (byte) 0x20, (byte) 0x32, (byte) 0x30, (byte) 0x30, - (byte) 0x33, (byte) 0x30, (byte) 0x39, (byte) 0x30, - (byte) 0x39, (byte) 0x00, (byte) 0x00, (byte) 0x00, /* capture pattern vorbis setup header */ - (byte) 0x00, (byte) 0x01, (byte) 0x05, (byte) 0x76, // .,.,5,v : start pos 146 - (byte) 0x6f, (byte) 0x72, (byte) 0x62, (byte) 0x69, // o,r,b,i - (byte) 0x73, (byte) 0x22, (byte) 0x42, (byte) 0x43, // s,. - (byte) 0x56, (byte) 0x01, (byte) 0x00, (byte) 0x40, - (byte) 0x00, (byte) 0x00, (byte) 0x18, (byte) 0x42, - (byte) 0x10, (byte) 0x2a, (byte) 0x05, (byte) 0xad, - (byte) 0x63, (byte) 0x8e, (byte) 0x3a, (byte) 0xc8, - (byte) 0x15, (byte) 0x21, (byte) 0x8c, (byte) 0x19, - (byte) 0xa2, (byte) 0xa0, (byte) 0x42, (byte) 0xca, - (byte) 0x29, (byte) 0xc7, (byte) 0x1d, (byte) 0x42, - (byte) 0xd0, (byte) 0x21, (byte) 0xa3, (byte) 0x24, - (byte) 0x43, (byte) 0x88, (byte) 0x3a, (byte) 0xc6, - (byte) 0x35, (byte) 0xc7, (byte) 0x18, (byte) 0x63, - (byte) 0x47, (byte) 0xb9, (byte) 0x64, (byte) 0x8a, - (byte) 0x42, (byte) 0xc9, (byte) 0x81, (byte) 0xd0, - (byte) 0x90, (byte) 0x55, (byte) 0x00, (byte) 0x00, - (byte) 0x40, (byte) 0x00, (byte) 0x00, (byte) 0xa4, - (byte) 0x1c, (byte) 0x57, (byte) 0x50, (byte) 0x72, - (byte) 0x49, (byte) 0x2d, (byte) 0xe7, (byte) 0x9c, - (byte) 0x73, (byte) 0xa3, (byte) 0x18, (byte) 0x57, - (byte) 0xcc, (byte) 0x71, (byte) 0xe8, (byte) 0x20, - (byte) 0xe7, (byte) 0x9c, (byte) 0x73, (byte) 0xe5, - (byte) 0x20, (byte) 0x67, (byte) 0xcc, (byte) 0x71, - (byte) 0x09, (byte) 0x25, (byte) 0xe7, (byte) 0x9c, - (byte) 0x73, (byte) 0x8e, (byte) 0x39, (byte) 0xe7, - (byte) 0x92, (byte) 0x72, (byte) 0x8e, (byte) 0x31, - (byte) 0xe7, (byte) 0x9c, (byte) 0x73, (byte) 0xa3, - (byte) 0x18, (byte) 0x57, (byte) 0x0e, (byte) 0x72, - (byte) 0x29, (byte) 0x2d, (byte) 0xe7, (byte) 0x9c, - (byte) 0x73, (byte) 0x81, (byte) 0x14, (byte) 0x47, - (byte) 0x8a, (byte) 0x71, (byte) 0xa7, (byte) 0x18, - (byte) 0xe7, (byte) 0x9c, (byte) 0x73, (byte) 0xa4, - (byte) 0x1c, (byte) 0x47, (byte) 0x8a, (byte) 0x71, - (byte) 0xa8, (byte) 0x18, (byte) 0xe7, (byte) 0x9c, - (byte) 0x73, (byte) 0x6d, (byte) 0x31, (byte) 0xb7, - (byte) 0x92, (byte) 0x72, (byte) 0xce, (byte) 0x39, - (byte) 0xe7, (byte) 0x9c, (byte) 0x73, (byte) 0xe6, - (byte) 0x20, (byte) 0x87, (byte) 0x52, (byte) 0x72, - (byte) 0xae, (byte) 0x35, (byte) 0xe7, (byte) 0x9c, - (byte) 0x73, (byte) 0xa4, (byte) 0x18, (byte) 0x67, - (byte) 0x0e, (byte) 0x72, (byte) 0x0b, (byte) 0x25, - (byte) 0xe7, (byte) 0x9c, (byte) 0x73, (byte) 0xc6, - (byte) 0x20, (byte) 0x67, (byte) 0xcc, (byte) 0x71, - (byte) 0xeb, (byte) 0x20, (byte) 0xe7, (byte) 0x9c, - (byte) 0x73, (byte) 0x8c, (byte) 0x35, (byte) 0xb7, - (byte) 0xd4, (byte) 0x72, (byte) 0xce, (byte) 0x39, - (byte) 0xe7, (byte) 0x9c, (byte) 0x73, (byte) 0xce, - (byte) 0x39, (byte) 0xe7, (byte) 0x9c, (byte) 0x73, - (byte) 0xce, (byte) 0x39, (byte) 0xe7, (byte) 0x9c, - (byte) 0x73, (byte) 0x8c, (byte) 0x31, (byte) 0xe7, - (byte) 0x9c, (byte) 0x73, (byte) 0xce, (byte) 0x39, - (byte) 0xe7, (byte) 0x9c, (byte) 0x73, (byte) 0x6e, - (byte) 0x31, (byte) 0xe7, (byte) 0x16, (byte) 0x73, - (byte) 0xae, (byte) 0x39, (byte) 0xe7, (byte) 0x9c, - (byte) 0x73, (byte) 0xce, (byte) 0x39, (byte) 0xe7, - (byte) 0x1c, (byte) 0x73, (byte) 0xce, (byte) 0x39, - (byte) 0xe7, (byte) 0x9c, (byte) 0x73, (byte) 0x20, - (byte) 0x34, (byte) 0x64, (byte) 0x15, (byte) 0x00, - (byte) 0x90, (byte) 0x00, (byte) 0x00, (byte) 0xa0, - (byte) 0xa1, (byte) 0x28, (byte) 0x8a, (byte) 0xe2, - (byte) 0x28, (byte) 0x0e, (byte) 0x10, (byte) 0x1a, - (byte) 0xb2, (byte) 0x0a, (byte) 0x00, (byte) 0xc8, - (byte) 0x00, (byte) 0x00, (byte) 0x10, (byte) 0x40, - (byte) 0x71, (byte) 0x14, (byte) 0x47, (byte) 0x91, - (byte) 0x14, (byte) 0x4b, (byte) 0xb1, (byte) 0x1c, - (byte) 0xcb, (byte) 0xd1, (byte) 0x24, (byte) 0x0d, - (byte) 0x08, (byte) 0x0d, (byte) 0x59, (byte) 0x05, - (byte) 0x00, (byte) 0x00, (byte) 0x01, (byte) 0x00, - (byte) 0x08, (byte) 0x00, (byte) 0x00, (byte) 0xa0, - (byte) 0x48, (byte) 0x86, (byte) 0xa4, (byte) 0x48, - (byte) 0x8a, (byte) 0xa5, (byte) 0x58, (byte) 0x8e, - (byte) 0x66, (byte) 0x69, (byte) 0x9e, (byte) 0x26, - (byte) 0x7a, (byte) 0xa2, (byte) 0x28, (byte) 0x9a, - (byte) 0xa2, (byte) 0x2a, (byte) 0xab, (byte) 0xb2, - (byte) 0x69, (byte) 0xca, (byte) 0xb2, (byte) 0x2c, - (byte) 0xcb, (byte) 0xb2, (byte) 0xeb, (byte) 0xba, - (byte) 0x2e, (byte) 0x10, (byte) 0x1a, (byte) 0xb2, - (byte) 0x0a, (byte) 0x00, (byte) 0x48, (byte) 0x00, - (byte) 0x00, (byte) 0x50, (byte) 0x51, (byte) 0x14, - (byte) 0xc5, (byte) 0x70, (byte) 0x14, (byte) 0x07, - (byte) 0x08, (byte) 0x0d, (byte) 0x59, (byte) 0x05, - (byte) 0x00, (byte) 0x64, (byte) 0x00, (byte) 0x00, - (byte) 0x08, (byte) 0x60, (byte) 0x28, (byte) 0x8a, - (byte) 0xa3, (byte) 0x38, (byte) 0x8e, (byte) 0xe4, - (byte) 0x58, (byte) 0x92, (byte) 0xa5, (byte) 0x59, - (byte) 0x9e, (byte) 0x07, (byte) 0x84, (byte) 0x86, - (byte) 0xac, (byte) 0x02, (byte) 0x00, (byte) 0x80, - (byte) 0x00, (byte) 0x00, (byte) 0x04, (byte) 0x00, - (byte) 0x00, (byte) 0x50, (byte) 0x0c, (byte) 0x47, - (byte) 0xb1, (byte) 0x14, (byte) 0x4d, (byte) 0xf1, - (byte) 0x24, (byte) 0xcf, (byte) 0xf2, (byte) 0x3c, - (byte) 0xcf, (byte) 0xf3, (byte) 0x3c, (byte) 0xcf, - (byte) 0xf3, (byte) 0x3c, (byte) 0xcf, (byte) 0xf3, - (byte) 0x3c, (byte) 0xcf, (byte) 0xf3, (byte) 0x3c, - (byte) 0xcf, (byte) 0xf3, (byte) 0x3c, (byte) 0xcf, - (byte) 0xf3, (byte) 0x3c, (byte) 0x0d, (byte) 0x08, - (byte) 0x0d, (byte) 0x59, (byte) 0x05, (byte) 0x00, - (byte) 0x20, (byte) 0x00, (byte) 0x00, (byte) 0x00, - (byte) 0x82, (byte) 0x28, (byte) 0x64, (byte) 0x18, - (byte) 0x03, (byte) 0x42, (byte) 0x43, (byte) 0x56, - (byte) 0x01, (byte) 0x00, (byte) 0x40, (byte) 0x00, - (byte) 0x00, (byte) 0x08, (byte) 0x21, (byte) 0x1a, - (byte) 0x19, (byte) 0x43, (byte) 0x9d, (byte) 0x52, - (byte) 0x12, (byte) 0x5c, (byte) 0x0a, (byte) 0x16, - (byte) 0x42, (byte) 0x1c, (byte) 0x11, (byte) 0x43, - (byte) 0x1d, (byte) 0x42, (byte) 0xce, (byte) 0x43, - (byte) 0xa9, (byte) 0xa5, (byte) 0x83, (byte) 0xe0, - (byte) 0x29, (byte) 0x85, (byte) 0x25, (byte) 0x63, - (byte) 0xd2, (byte) 0x53, (byte) 0xac, (byte) 0x41, - (byte) 0x08, (byte) 0x21, (byte) 0x7c, (byte) 0xef, - (byte) 0x3d, (byte) 0xf7, (byte) 0xde, (byte) 0x7b, - (byte) 0xef, (byte) 0x81, (byte) 0xd0, (byte) 0x90, - (byte) 0x55, (byte) 0x00, (byte) 0x00, (byte) 0x10, - (byte) 0x00, (byte) 0x00, (byte) 0x61, (byte) 0x14, - (byte) 0x38, (byte) 0x88, (byte) 0x81, (byte) 0xc7, - (byte) 0x24, (byte) 0x08, (byte) 0x21, (byte) 0x84, - (byte) 0x62, (byte) 0x14, (byte) 0x27, (byte) 0x44, - (byte) 0x71, (byte) 0xa6, (byte) 0x20, (byte) 0x08, - (byte) 0x21, (byte) 0x84, (byte) 0xe5, (byte) 0x24, - (byte) 0x58, (byte) 0xca, (byte) 0x79, (byte) 0xe8, - (byte) 0x24, (byte) 0x08, (byte) 0xdd, (byte) 0x83, - (byte) 0x10, (byte) 0x42, (byte) 0xb8, (byte) 0x9c, - (byte) 0x7b, (byte) 0xcb, (byte) 0xb9, (byte) 0xf7, - (byte) 0xde, (byte) 0x7b, (byte) 0x20, (byte) 0x34, - (byte) 0x64, (byte) 0x15, (byte) 0x00, (byte) 0x00, - (byte) 0x08, (byte) 0x00, (byte) 0xc0, (byte) 0x20, - (byte) 0x84, (byte) 0x10, (byte) 0x42, (byte) 0x08, - (byte) 0x21, (byte) 0x84, (byte) 0x10, (byte) 0x42, - (byte) 0x08, (byte) 0x29, (byte) 0xa4, (byte) 0x94, - (byte) 0x52, (byte) 0x48, (byte) 0x29, (byte) 0xa6, - (byte) 0x98, (byte) 0x62, (byte) 0x8a, (byte) 0x29, - (byte) 0xc7, (byte) 0x1c, (byte) 0x73, (byte) 0xcc, - (byte) 0x31, (byte) 0xc7, (byte) 0x20, (byte) 0x83, - (byte) 0x0c, (byte) 0x32, (byte) 0xe8, (byte) 0xa0, - (byte) 0x93, (byte) 0x4e, (byte) 0x3a, (byte) 0xc9, - (byte) 0xa4, (byte) 0x92, (byte) 0x4e, (byte) 0x3a, - (byte) 0xca, (byte) 0x24, (byte) 0xa3, (byte) 0x8e, - (byte) 0x52, (byte) 0x6b, (byte) 0x29, (byte) 0xb5, - (byte) 0x14, (byte) 0x53, (byte) 0x4c, (byte) 0xb1, - (byte) 0xe5, (byte) 0x16, (byte) 0x63, (byte) 0xad, - (byte) 0xb5, (byte) 0xd6, (byte) 0x9c, (byte) 0x73, - (byte) 0xaf, (byte) 0x41, (byte) 0x29, (byte) 0x63, - (byte) 0x8c, (byte) 0x31, (byte) 0xc6, (byte) 0x18, - (byte) 0x63, (byte) 0x8c, (byte) 0x31, (byte) 0xc6, - (byte) 0x18, (byte) 0x63, (byte) 0x8c, (byte) 0x31, - (byte) 0xc6, (byte) 0x18, (byte) 0x23, (byte) 0x08, - (byte) 0x0d, (byte) 0x59, (byte) 0x05, (byte) 0x00, - (byte) 0x80, (byte) 0x00, (byte) 0x00, (byte) 0x10, - (byte) 0x06, (byte) 0x19, (byte) 0x64, (byte) 0x90, - (byte) 0x41, (byte) 0x08, (byte) 0x21, (byte) 0x84, - (byte) 0x14, (byte) 0x52, (byte) 0x48, (byte) 0x29, - (byte) 0xa6, (byte) 0x98, (byte) 0x72, (byte) 0xcc, - (byte) 0x31, (byte) 0xc7, (byte) 0x1c, (byte) 0x03, - (byte) 0x42, (byte) 0x43, (byte) 0x56, (byte) 0x01, - (byte) 0x00, (byte) 0x80, (byte) 0x00, (byte) 0x00, - (byte) 0x02, (byte) 0x00, (byte) 0x00, (byte) 0x00, - (byte) 0x1c, (byte) 0x45, (byte) 0x52, (byte) 0x24, - (byte) 0x47, (byte) 0x72, (byte) 0x24, (byte) 0x47, - (byte) 0x92, (byte) 0x24, (byte) 0xc9, (byte) 0x92, - (byte) 0x2c, (byte) 0x49, (byte) 0x93, (byte) 0x3c, - (byte) 0xcb, (byte) 0xb3, (byte) 0x3c, (byte) 0xcb, - (byte) 0xb3, (byte) 0x3c, (byte) 0x4d, (byte) 0xd4, - (byte) 0x44, (byte) 0x4d, (byte) 0x15, (byte) 0x55, - (byte) 0xd5, (byte) 0x55, (byte) 0x6d, (byte) 0xd7, - (byte) 0xf6, (byte) 0x6d, (byte) 0x5f, (byte) 0xf6, - (byte) 0x6d, (byte) 0xdf, (byte) 0xd5, (byte) 0x65, - (byte) 0xdf, (byte) 0xf6, (byte) 0x65, (byte) 0xdb, - (byte) 0xd5, (byte) 0x65, (byte) 0x5d, (byte) 0x96, - (byte) 0x65, (byte) 0xdd, (byte) 0xb5, (byte) 0x6d, - (byte) 0x5d, (byte) 0xd6, (byte) 0x5d, (byte) 0x5d, - (byte) 0xd7, (byte) 0x75, (byte) 0x5d, (byte) 0xd7, - (byte) 0x75, (byte) 0x5d, (byte) 0xd7, (byte) 0x75, - (byte) 0x5d, (byte) 0xd7, (byte) 0x75, (byte) 0x5d, - (byte) 0xd7, (byte) 0x75, (byte) 0x5d, (byte) 0xd7, - (byte) 0x81, (byte) 0xd0, (byte) 0x90, (byte) 0x55, - (byte) 0x00, (byte) 0x80, (byte) 0x04, (byte) 0x00, - (byte) 0x80, (byte) 0x8e, (byte) 0xe4, (byte) 0x38, - (byte) 0x8e, (byte) 0xe4, (byte) 0x38, (byte) 0x8e, - (byte) 0xe4, (byte) 0x48, (byte) 0x8e, (byte) 0xa4, - (byte) 0x48, (byte) 0x0a, (byte) 0x10, (byte) 0x1a, - (byte) 0xb2, (byte) 0x0a, (byte) 0x00, (byte) 0x90, - (byte) 0x01, (byte) 0x00, (byte) 0x10, (byte) 0x00, - (byte) 0x80, (byte) 0xa3, (byte) 0x38, (byte) 0x8a, - (byte) 0xe3, (byte) 0x48, (byte) 0x8e, (byte) 0xe4, - (byte) 0x58, (byte) 0x8e, (byte) 0x25, (byte) 0x59, - (byte) 0x92, (byte) 0x26, (byte) 0x69, (byte) 0x96, - (byte) 0x67, (byte) 0x79, (byte) 0x96, (byte) 0xa7, - (byte) 0x79, (byte) 0x9a, (byte) 0xa8, (byte) 0x89, - (byte) 0x1e, (byte) 0x10, (byte) 0x1a, (byte) 0xb2, - (byte) 0x0a, (byte) 0x00, (byte) 0x00, (byte) 0x04, - (byte) 0x00, (byte) 0x10, (byte) 0x00, (byte) 0x00, - (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x80, - (byte) 0xa2, (byte) 0x28, (byte) 0x8a, (byte) 0xa3, - (byte) 0x38, (byte) 0x8e, (byte) 0x24, (byte) 0x59, - (byte) 0x96, (byte) 0xa6, (byte) 0x69, (byte) 0x9e, - (byte) 0xa7, (byte) 0x7a, (byte) 0xa2, (byte) 0x28, - (byte) 0x9a, (byte) 0xaa, (byte) 0xaa, (byte) 0x8a, - (byte) 0xa6, (byte) 0xa9, (byte) 0xaa, (byte) 0xaa, - (byte) 0x6a, (byte) 0x9a, (byte) 0xa6, (byte) 0x69, - (byte) 0x9a, (byte) 0xa6, (byte) 0x69, (byte) 0x9a, - (byte) 0xa6, (byte) 0x69, (byte) 0x9a, (byte) 0xa6, - (byte) 0x69, (byte) 0x9a, (byte) 0xa6, (byte) 0x69, - (byte) 0x9a, (byte) 0xa6, (byte) 0x69, (byte) 0x9a, - (byte) 0xa6, (byte) 0x69, (byte) 0x9a, (byte) 0xa6, - (byte) 0x69, (byte) 0x9a, (byte) 0xa6, (byte) 0x69, - (byte) 0x9a, (byte) 0xa6, (byte) 0x69, (byte) 0x9a, - (byte) 0xa6, (byte) 0x69, (byte) 0x02, (byte) 0xa1, - (byte) 0x21, (byte) 0xab, (byte) 0x00, (byte) 0x00, - (byte) 0x09, (byte) 0x00, (byte) 0x00, (byte) 0x1d, - (byte) 0xc7, (byte) 0x71, (byte) 0x1c, (byte) 0x47, - (byte) 0x71, (byte) 0x1c, (byte) 0xc7, (byte) 0x71, - (byte) 0x24, (byte) 0x47, (byte) 0x92, (byte) 0x24, - (byte) 0x20, (byte) 0x34, (byte) 0x64, (byte) 0x15, - (byte) 0x00, (byte) 0x20, (byte) 0x03, (byte) 0x00, - (byte) 0x20, (byte) 0x00, (byte) 0x00, (byte) 0x43, - (byte) 0x51, (byte) 0x1c, (byte) 0x45, (byte) 0x72, - (byte) 0x2c, (byte) 0xc7, (byte) 0x92, (byte) 0x34, - (byte) 0x4b, (byte) 0xb3, (byte) 0x3c, (byte) 0xcb, - (byte) 0xd3, (byte) 0x44, (byte) 0xcf, (byte) 0xf4, - (byte) 0x5c, (byte) 0x51, (byte) 0x36, (byte) 0x75, - (byte) 0x53, (byte) 0x57, (byte) 0x6d, (byte) 0x20, - (byte) 0x34, (byte) 0x64, (byte) 0x15, (byte) 0x00, - (byte) 0x00, (byte) 0x08, (byte) 0x00, (byte) 0x20, - (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, - (byte) 0x00, (byte) 0x00, (byte) 0xc7, (byte) 0x73, - (byte) 0x3c, (byte) 0xc7, (byte) 0x73, (byte) 0x3c, - (byte) 0xc9, (byte) 0x93, (byte) 0x3c, (byte) 0xcb, - (byte) 0x73, (byte) 0x3c, (byte) 0xc7, (byte) 0x93, - (byte) 0x3c, (byte) 0x49, (byte) 0xd3, (byte) 0x34, - (byte) 0x4d, (byte) 0xd3, (byte) 0x34, (byte) 0x4d, - (byte) 0xd3, (byte) 0x34, (byte) 0x4d, (byte) 0xd3, - (byte) 0x34, (byte) 0x4d, (byte) 0xd3, (byte) 0x34, - (byte) 0x4d, (byte) 0xd3, (byte) 0x34, (byte) 0x4d, - (byte) 0xd3, (byte) 0x34, (byte) 0x4d, (byte) 0xd3, - (byte) 0x34, (byte) 0x4d, (byte) 0xd3, (byte) 0x34, - (byte) 0x4d, (byte) 0xd3, (byte) 0x34, (byte) 0x4d, - (byte) 0xd3, (byte) 0x34, (byte) 0x4d, (byte) 0xd3, - (byte) 0x34, (byte) 0x4d, (byte) 0xd3, (byte) 0x34, - (byte) 0x4d, (byte) 0xd3, (byte) 0x34, (byte) 0x4d, - (byte) 0x03, (byte) 0x42, (byte) 0x43, (byte) 0x56, - (byte) 0x02, (byte) 0x00, (byte) 0x64, (byte) 0x00, - (byte) 0x00, (byte) 0x90, (byte) 0x02, (byte) 0xcf, - (byte) 0x42, (byte) 0x29, (byte) 0x2d, (byte) 0x46, - (byte) 0x02, (byte) 0x1c, (byte) 0x88, (byte) 0x98, - (byte) 0xa3, (byte) 0xd8, (byte) 0x7b, (byte) 0xef, - (byte) 0xbd, (byte) 0xf7, (byte) 0xde, (byte) 0x7b, - (byte) 0x65, (byte) 0x3c, (byte) 0x92, (byte) 0x88, - (byte) 0x49, (byte) 0xed, (byte) 0x31, (byte) 0xf4, - (byte) 0xd4, (byte) 0x31, (byte) 0x07, (byte) 0xb1, - (byte) 0x67, (byte) 0xc6, (byte) 0x23, (byte) 0x66, - (byte) 0x94, (byte) 0xa3, (byte) 0xd8, (byte) 0x29, - (byte) 0xcf, (byte) 0x1c, (byte) 0x42, (byte) 0x0c, - (byte) 0x62, (byte) 0xe8, (byte) 0x3c, (byte) 0x74, - (byte) 0x4a, (byte) 0x31, (byte) 0x88, (byte) 0x29, - (byte) 0xf5, (byte) 0x52, (byte) 0x32, (byte) 0xc6, - (byte) 0x20, (byte) 0xc6, (byte) 0xd8, (byte) 0x63, - (byte) 0x0c, (byte) 0x21, (byte) 0x94, (byte) 0x18, - (byte) 0x08, (byte) 0x0d, (byte) 0x59, (byte) 0x21, - (byte) 0x00, (byte) 0x84, (byte) 0x66, (byte) 0x00, - (byte) 0x18, (byte) 0x24, (byte) 0x09, (byte) 0x90, - (byte) 0x34, (byte) 0x0d, (byte) 0x90, (byte) 0x34, - (byte) 0x0d, (byte) 0x00, (byte) 0x00, (byte) 0x00, - (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, - (byte) 0x24, (byte) 0x4f, (byte) 0x03, (byte) 0x34, - (byte) 0x51, (byte) 0x04, (byte) 0x34, (byte) 0x4f, - (byte) 0x04, (byte) 0x00, (byte) 0x00, (byte) 0x00, - (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, - (byte) 0x49, (byte) 0xf3, (byte) 0x00, (byte) 0x4d, - (byte) 0xf4, (byte) 0x00, (byte) 0x4d, (byte) 0x14, - (byte) 0x01, (byte) 0x00, (byte) 0x00, (byte) 0x00, - (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, - (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, - (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, - (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, - (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, - (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, - (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, - (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, - (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, - (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, - (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, - (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, - (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, - (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, - (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, - (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, - (byte) 0x90, (byte) 0x3c, (byte) 0x0d, (byte) 0xf0, - (byte) 0x44, (byte) 0x11, (byte) 0xd0, (byte) 0x44, - (byte) 0x11, (byte) 0x00, (byte) 0x00, (byte) 0x00, - (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, - (byte) 0x34, (byte) 0x51, (byte) 0x04, (byte) 0x44, - (byte) 0x51, (byte) 0x05, (byte) 0x44, (byte) 0xd5, - (byte) 0x04, (byte) 0x00, (byte) 0x00, (byte) 0x00, - (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, - (byte) 0x4d, (byte) 0x14, (byte) 0x01, (byte) 0x4f, - (byte) 0x15, (byte) 0x01, (byte) 0xd1, (byte) 0x54, - (byte) 0x01, (byte) 0x00, (byte) 0x00, (byte) 0x00, - (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, - (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, - (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, - (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, - (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, - (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, - (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, - (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, - (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, - (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, - (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, - (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, - (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, - (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, - (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, - (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, - (byte) 0x90, (byte) 0x34, (byte) 0x0f, (byte) 0xd0, - (byte) 0x44, (byte) 0x11, (byte) 0xf0, (byte) 0x44, - (byte) 0x11, (byte) 0x00, (byte) 0x00, (byte) 0x00, - (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, - (byte) 0x34, (byte) 0x51, (byte) 0x04, (byte) 0x44, - (byte) 0xd5, (byte) 0x04, (byte) 0x3c, (byte) 0x51, - (byte) 0x05, (byte) 0x00, (byte) 0x00, (byte) 0x00, - (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, - (byte) 0x4d, (byte) 0x14, (byte) 0x01, (byte) 0xd1, - (byte) 0x54, (byte) 0x01, (byte) 0x51, (byte) 0x15, - (byte) 0x01, (byte) 0x00, (byte) 0x00, (byte) 0x00, - (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, - (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, - (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, - (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, - (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, - (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, - (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, - (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, - (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, - (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, - (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, - (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, - (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, - (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, - (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, - (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, - (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, - (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, - (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, - (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, - (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, - (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, - (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, - (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, - (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, - (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, - (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, - (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, - (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, - (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, - (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, - (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, - (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, - (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, - (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, - (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, - (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, - (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, - (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, - (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, - (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, - (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, - (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, - (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, - (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, - (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, - (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, - (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, - (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, - (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, - (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, - (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, - (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, - (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, - (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, - (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, - (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, - (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, - (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, - (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, - (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, - (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, - (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, - (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, - (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, - (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, - (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, - (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, - (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, - (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, - (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, - (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, - (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, - (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, - (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, - (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, - (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, - (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, - (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, - (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, - (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, - (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, - (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, - (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, - (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, - (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, - (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, - (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, - (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, - (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, - (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, - (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, - (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, - (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, - (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, - (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, - (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, - (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, - (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, - (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, - (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, - (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, - (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, - (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, - (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, - (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, - (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, - (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, - (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, - (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, - (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, - (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, - (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, - (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, - (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, - (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, - (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, - (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, - (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, - (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, - (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, - (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, - (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, - (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, - (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, - (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, - (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, - (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, - (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, - (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, - (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, - (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, - (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, - (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, - (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, - (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, - (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, - (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, - (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, - (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, - (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, - (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, - (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, - (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, - (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, - (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, - (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, - (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, - (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, - (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, - (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, - (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, - (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x04, - (byte) 0x00, (byte) 0x00, (byte) 0x04, (byte) 0x38, - (byte) 0x00, (byte) 0x00, (byte) 0x04, (byte) 0x58, - (byte) 0x08, (byte) 0x85, (byte) 0x86, (byte) 0xac, - (byte) 0x08, (byte) 0x00, (byte) 0xe2, (byte) 0x04, - (byte) 0x00, (byte) 0x04, (byte) 0x00, (byte) 0x00, - (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, - (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, - (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, - (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, - (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, - (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, - (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, - (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, - (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, - (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, - (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, - (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, - (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, - (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, - (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, - (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, - (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, - (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, - (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x10, - (byte) 0x00, (byte) 0x00, (byte) 0x30, (byte) 0xe0, - (byte) 0x00, (byte) 0x00, (byte) 0x10, (byte) 0x60, - (byte) 0x42, (byte) 0x19, (byte) 0x28, (byte) 0x34, - (byte) 0x64, (byte) 0x45, (byte) 0x00, (byte) 0x10, - (byte) 0x27, (byte) 0x00, (byte) 0x60, (byte) 0x70, - (byte) 0x1c, (byte) 0xcb, (byte) 0x02, (byte) 0x00, - (byte) 0x00, (byte) 0x47, (byte) 0x92, (byte) 0x34, - (byte) 0x0d, (byte) 0x00, (byte) 0x00, (byte) 0x1c, - (byte) 0x49, (byte) 0xd2, (byte) 0x34, (byte) 0x00, - (byte) 0x00, (byte) 0xd0, (byte) 0x34, (byte) 0x4d, - (byte) 0x14, (byte) 0x01, (byte) 0x00, (byte) 0xc0, - (byte) 0xd2, (byte) 0x34, (byte) 0x51, (byte) 0x04, - (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, - (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, - (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, - (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, - (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, - (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, - (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, - (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, - (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, - (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, - (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, - (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, - (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, - (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, - (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, - (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, - (byte) 0x10, (byte) 0x00, (byte) 0x00, (byte) 0x30, - (byte) 0xe0, (byte) 0x00, (byte) 0x00, (byte) 0x10, - (byte) 0x60, (byte) 0x42, (byte) 0x19, (byte) 0x28, - (byte) 0x34, (byte) 0x64, (byte) 0x25, (byte) 0x00, - (byte) 0x10, (byte) 0x05, (byte) 0x00, (byte) 0x60, - (byte) 0x30, (byte) 0x14, (byte) 0x4d, (byte) 0x03, - (byte) 0x58, (byte) 0x16, (byte) 0xc0, (byte) 0xb2, - (byte) 0x00, (byte) 0x9a, (byte) 0x06, (byte) 0xd0, - (byte) 0x34, (byte) 0x80, (byte) 0xe7, (byte) 0x01, - (byte) 0x3c, (byte) 0x11, (byte) 0x60, (byte) 0x9a, - (byte) 0x00, (byte) 0x40, (byte) 0x00, (byte) 0x00, - (byte) 0x40, (byte) 0x81, (byte) 0x03, (byte) 0x00, - (byte) 0x40, (byte) 0x80, (byte) 0x0d, (byte) 0x9a, - (byte) 0x12, (byte) 0x8b, (byte) 0x03, (byte) 0x14, - (byte) 0x1a, (byte) 0xb2, (byte) 0x12, (byte) 0x00, - (byte) 0x88, (byte) 0x02, (byte) 0x00, (byte) 0x30, - (byte) 0x28, (byte) 0x8a, (byte) 0x24, (byte) 0x59, - (byte) 0x96, (byte) 0xe7, (byte) 0x41, (byte) 0xd3, - (byte) 0x34, (byte) 0x4d, (byte) 0x14, (byte) 0xa1, - (byte) 0x69, (byte) 0x9a, (byte) 0x26, (byte) 0x8a, - (byte) 0xf0, (byte) 0x3c, (byte) 0xcf, (byte) 0x13, - (byte) 0x45, (byte) 0x78, (byte) 0x9e, (byte) 0xe7, - (byte) 0x99, (byte) 0x26, (byte) 0x44, (byte) 0xd1, - (byte) 0xf3, (byte) 0x4c, (byte) 0x13, (byte) 0xa2, - (byte) 0xe8, (byte) 0x79, (byte) 0xa6, (byte) 0x09, - (byte) 0xd3, (byte) 0x14, (byte) 0x45, (byte) 0xd3, - (byte) 0x04, (byte) 0xa2, (byte) 0x68, (byte) 0x9a, - (byte) 0x02, (byte) 0x00, (byte) 0x00, (byte) 0x0a, - (byte) 0x1c, (byte) 0x00, (byte) 0x00, (byte) 0x02, - (byte) 0x6c, (byte) 0xd0, (byte) 0x94, (byte) 0x58, - (byte) 0x1c, (byte) 0xa0, (byte) 0xd0, (byte) 0x90, - (byte) 0x95, (byte) 0x00, (byte) 0x40, (byte) 0x48, - (byte) 0x00, (byte) 0x80, (byte) 0x41, (byte) 0x51, - (byte) 0x2c, (byte) 0xcb, (byte) 0xf3, (byte) 0x44, - (byte) 0x51, (byte) 0x14, (byte) 0x4d, (byte) 0x53, - (byte) 0x55, (byte) 0x5d, (byte) 0x17, (byte) 0x9a, - (byte) 0xe6, (byte) 0x79, (byte) 0xa2, (byte) 0x28, - (byte) 0x8a, (byte) 0xa6, (byte) 0xa9, (byte) 0xaa, - (byte) 0xae, (byte) 0x0b, (byte) 0x4d, (byte) 0xf3, - (byte) 0x3c, (byte) 0x51, (byte) 0x14, (byte) 0x45, - (byte) 0xd3, (byte) 0x54, (byte) 0x55, (byte) 0xd7, - (byte) 0x85, (byte) 0xe7, (byte) 0x79, (byte) 0xa2, - (byte) 0x29, (byte) 0x9a, (byte) 0xa6, (byte) 0x69, - (byte) 0xaa, (byte) 0xaa, (byte) 0xeb, (byte) 0xc2, - (byte) 0xf3, (byte) 0x44, (byte) 0xd1, (byte) 0x34, - (byte) 0x4d, (byte) 0x53, (byte) 0x55, (byte) 0x55, - (byte) 0xd7, (byte) 0x75, (byte) 0xe1, (byte) 0x79, - (byte) 0xa2, (byte) 0x68, (byte) 0x9a, (byte) 0xa6, - (byte) 0xa9, (byte) 0xaa, (byte) 0xae, (byte) 0xeb, - (byte) 0xba, (byte) 0xf0, (byte) 0x3c, (byte) 0x51, - (byte) 0x34, (byte) 0x4d, (byte) 0xd3, (byte) 0x54, - (byte) 0x55, (byte) 0xd7, (byte) 0x95, (byte) 0x65, - (byte) 0x88, (byte) 0xa2, (byte) 0x28, (byte) 0x9a, - (byte) 0xa6, (byte) 0x69, (byte) 0xaa, (byte) 0xaa, - (byte) 0xeb, (byte) 0xca, (byte) 0x32, (byte) 0x10, - (byte) 0x45, (byte) 0xd3, (byte) 0x34, (byte) 0x4d, - (byte) 0x55, (byte) 0x75, (byte) 0x5d, (byte) 0x59, - (byte) 0x06, (byte) 0xa2, (byte) 0x68, (byte) 0x9a, - (byte) 0xaa, (byte) 0xea, (byte) 0xba, (byte) 0xae, - (byte) 0x2b, (byte) 0xcb, (byte) 0x40, (byte) 0x14, - (byte) 0x4d, (byte) 0x53, (byte) 0x55, (byte) 0x5d, - (byte) 0xd7, (byte) 0x75, (byte) 0x65, (byte) 0x19, - (byte) 0x98, (byte) 0xa6, (byte) 0x6a, (byte) 0xaa, - (byte) 0xaa, (byte) 0xeb, (byte) 0xca, (byte) 0xb2, - (byte) 0x2c, (byte) 0x03, (byte) 0x4c, (byte) 0x53, - (byte) 0x55, (byte) 0x5d, (byte) 0x57, (byte) 0x96, - (byte) 0x65, (byte) 0x19, (byte) 0xa0, (byte) 0xaa, - (byte) 0xae, (byte) 0xeb, (byte) 0xba, (byte) 0xb2, - (byte) 0x6c, (byte) 0xdb, (byte) 0x00, (byte) 0x55, - (byte) 0x75, (byte) 0x5d, (byte) 0xd7, (byte) 0x95, - (byte) 0x65, (byte) 0xdb, (byte) 0x06, (byte) 0xb8, - (byte) 0xae, (byte) 0xeb, (byte) 0xca, (byte) 0xb2, - (byte) 0x2c, (byte) 0xdb, (byte) 0x36, (byte) 0x00, - (byte) 0xd7, (byte) 0x95, (byte) 0x65, (byte) 0x59, - (byte) 0xb6, (byte) 0x6d, (byte) 0x01, (byte) 0x00, - (byte) 0x00, (byte) 0x07, (byte) 0x0e, (byte) 0x00, - (byte) 0x00, (byte) 0x01, (byte) 0x46, (byte) 0xd0, - (byte) 0x49, (byte) 0x46, (byte) 0x95, (byte) 0x45, - (byte) 0xd8, (byte) 0x68, (byte) 0xc2, (byte) 0x85, - (byte) 0x07, (byte) 0xa0, (byte) 0xd0, (byte) 0x90, - (byte) 0x15, (byte) 0x01, (byte) 0x40, (byte) 0x14, - (byte) 0x00, (byte) 0x00, (byte) 0x60, (byte) 0x8c, - (byte) 0x52, (byte) 0x8a, (byte) 0x29, (byte) 0x65, - (byte) 0x18, (byte) 0x93, (byte) 0x50, (byte) 0x4a, - (byte) 0x09, (byte) 0x0d, (byte) 0x63, (byte) 0x52, - (byte) 0x4a, (byte) 0x2a, (byte) 0xa5, (byte) 0x92, - (byte) 0x92, (byte) 0x52, (byte) 0x4a, (byte) 0xa5, - (byte) 0x54, (byte) 0x12, (byte) 0x52, (byte) 0x4a, - (byte) 0xa9, (byte) 0x94, (byte) 0x4a, (byte) 0x4a, - (byte) 0x4a, (byte) 0x29, (byte) 0x95, (byte) 0x92, - (byte) 0x51, (byte) 0x4a, (byte) 0x29, (byte) 0xb5, - (byte) 0x96, (byte) 0x2a, (byte) 0x29, (byte) 0xa9, - (byte) 0x94, (byte) 0x94, (byte) 0x52, (byte) 0x25, - (byte) 0xa5, (byte) 0xa4, (byte) 0x92, (byte) 0x52, - (byte) 0x2a, (byte) 0x00, (byte) 0x00, (byte) 0xec, - (byte) 0xc0, (byte) 0x01, (byte) 0x00, (byte) 0xec, - (byte) 0xc0, (byte) 0x42, (byte) 0x28, (byte) 0x34, - (byte) 0x64, (byte) 0x25, (byte) 0x00, (byte) 0x90, - (byte) 0x07, (byte) 0x00, (byte) 0x40, (byte) 0x10, - (byte) 0x82, (byte) 0x14, (byte) 0x63, (byte) 0x8c, - (byte) 0x39, (byte) 0x27, (byte) 0xa5, (byte) 0x54, - (byte) 0x8a, (byte) 0x31, (byte) 0xe7, (byte) 0x9c, - (byte) 0x93, (byte) 0x52, (byte) 0x2a, (byte) 0xc5, - (byte) 0x98, (byte) 0x73, (byte) 0xce, (byte) 0x49, - (byte) 0x29, (byte) 0x19, (byte) 0x63, (byte) 0xcc, - (byte) 0x39, (byte) 0xe7, (byte) 0xa4, (byte) 0x94, - (byte) 0x8c, (byte) 0x31, (byte) 0xe6, (byte) 0x9c, - (byte) 0x73, (byte) 0x52, (byte) 0x4a, (byte) 0xc6, - (byte) 0x9c, (byte) 0x73, (byte) 0xce, (byte) 0x39, - (byte) 0x29, (byte) 0x25, (byte) 0x63, (byte) 0xce, - (byte) 0x39, (byte) 0xe7, (byte) 0x9c, (byte) 0x94, - (byte) 0xd2, (byte) 0x39, (byte) 0xe7, (byte) 0x9c, - (byte) 0x83, (byte) 0x50, (byte) 0x4a, (byte) 0x29, - (byte) 0xa5, (byte) 0x73, (byte) 0xce, (byte) 0x41, - (byte) 0x28, (byte) 0xa5, (byte) 0x94, (byte) 0x12, - (byte) 0x42, (byte) 0xe7, (byte) 0x20, (byte) 0x94, - (byte) 0x52, (byte) 0x4a, (byte) 0xe9, (byte) 0x9c, - (byte) 0x73, (byte) 0x10, (byte) 0x0a, (byte) 0x00, - (byte) 0x00, (byte) 0x2a, (byte) 0x70, (byte) 0x00, - (byte) 0x00, (byte) 0x08, (byte) 0xb0, (byte) 0x51, - (byte) 0x64, (byte) 0x73, (byte) 0x82, (byte) 0x91, - (byte) 0xa0, (byte) 0x42, (byte) 0x43, (byte) 0x56, - (byte) 0x02, (byte) 0x00, (byte) 0xa9, (byte) 0x00, - (byte) 0x00, (byte) 0x06, (byte) 0xc7, (byte) 0xb1, - (byte) 0x2c, (byte) 0x4d, (byte) 0xd3, (byte) 0x34, - (byte) 0xcf, (byte) 0x13, (byte) 0x45, (byte) 0x4b, - (byte) 0x92, (byte) 0x34, (byte) 0xcf, (byte) 0x13, - (byte) 0x3d, (byte) 0x4f, (byte) 0x14, (byte) 0x4d, - (byte) 0xd5, (byte) 0x92, (byte) 0x24, (byte) 0xcf, - (byte) 0x13, (byte) 0x45, (byte) 0xcf, (byte) 0x13, - (byte) 0x4d, (byte) 0x53, (byte) 0xe5, (byte) 0x79, - (byte) 0x9e, (byte) 0x28, (byte) 0x8a, (byte) 0xa2, - (byte) 0x68, (byte) 0x9a, (byte) 0xaa, (byte) 0x4a, - (byte) 0x14, (byte) 0x45, (byte) 0x4f, (byte) 0x14, - (byte) 0x45, (byte) 0xd1, (byte) 0x34, (byte) 0x55, - (byte) 0x95, (byte) 0x2c, (byte) 0x8b, (byte) 0xa2, - (byte) 0x69, (byte) 0x9a, (byte) 0xa6, (byte) 0xaa, - (byte) 0xba, (byte) 0x2e, (byte) 0x5b, (byte) 0x16, - (byte) 0x45, (byte) 0xd3, (byte) 0x34, (byte) 0x4d, - (byte) 0x55, (byte) 0x75, (byte) 0x5d, (byte) 0x98, - (byte) 0xa6, (byte) 0x28, (byte) 0xaa, (byte) 0xaa, - (byte) 0xeb, (byte) 0xca, (byte) 0x2e, (byte) 0x4c, - (byte) 0x53, (byte) 0x14, (byte) 0x4d, (byte) 0xd3, - (byte) 0x75, (byte) 0x65, (byte) 0x19, (byte) 0xb2, - (byte) 0xad, (byte) 0x9a, (byte) 0xaa, (byte) 0xea, - (byte) 0xba, (byte) 0xb2, (byte) 0x0d, (byte) 0xdb, - (byte) 0x36, (byte) 0x4d, (byte) 0x55, (byte) 0x75, - (byte) 0x5d, (byte) 0x59, (byte) 0x06, (byte) 0xae, - (byte) 0xeb, (byte) 0xba, (byte) 0xb2, (byte) 0x6c, - (byte) 0xeb, (byte) 0xc0, (byte) 0x75, (byte) 0x5d, - (byte) 0x57, (byte) 0x96, (byte) 0x6d, (byte) 0x5d, - (byte) 0x00, (byte) 0x00, (byte) 0x78, (byte) 0x82, - (byte) 0x03, (byte) 0x00, (byte) 0x50, (byte) 0x81, - (byte) 0x0d, (byte) 0xab, (byte) 0x23, (byte) 0x9c, - (byte) 0x14, (byte) 0x8d, (byte) 0x05, (byte) 0x16, - (byte) 0x1a, (byte) 0xb2, (byte) 0x12, (byte) 0x00, - (byte) 0xc8, (byte) 0x00, (byte) 0x00, (byte) 0x20, - (byte) 0x08, (byte) 0x41, (byte) 0x48, (byte) 0x29, - (byte) 0x85, (byte) 0x90, (byte) 0x52, (byte) 0x0a, - (byte) 0x21, (byte) 0xa5, (byte) 0x14, (byte) 0x42, - (byte) 0x4a, (byte) 0x29, (byte) 0x84, (byte) 0x04, - (byte) 0x00, (byte) 0x00, (byte) 0x0c, (byte) 0x38, - (byte) 0x00, (byte) 0x00, (byte) 0x04, (byte) 0x98, - (byte) 0x50, (byte) 0x06, (byte) 0x0a, (byte) 0x0d, - (byte) 0x59, (byte) 0x09, (byte) 0x00, (byte) 0xa4, - (byte) 0x02, (byte) 0x00, (byte) 0x00, (byte) 0x10, - (byte) 0x42, (byte) 0x08, (byte) 0x21, (byte) 0x84, - (byte) 0x10, (byte) 0x42, (byte) 0x08, (byte) 0x21, - (byte) 0x84, (byte) 0x10, (byte) 0x42, (byte) 0x08, - (byte) 0x21, (byte) 0x84, (byte) 0x10, (byte) 0x42, - (byte) 0x08, (byte) 0x21, (byte) 0x84, (byte) 0x10, - (byte) 0x42, (byte) 0x08, (byte) 0x21, (byte) 0x84, - (byte) 0x10, (byte) 0x42, (byte) 0x08, (byte) 0x21, - (byte) 0x84, (byte) 0x10, (byte) 0x42, (byte) 0x08, - (byte) 0x21, (byte) 0x84, (byte) 0x10, (byte) 0x42, - (byte) 0x08, (byte) 0x21, (byte) 0x84, (byte) 0x10, - (byte) 0x42, (byte) 0x08, (byte) 0x21, (byte) 0x84, - (byte) 0x10, (byte) 0x42, (byte) 0x08, (byte) 0x21, - (byte) 0x84, (byte) 0xce, (byte) 0x39, (byte) 0xe7, - (byte) 0x9c, (byte) 0x73, (byte) 0xce, (byte) 0x39, - (byte) 0xe7, (byte) 0x9c, (byte) 0x73, (byte) 0xce, - (byte) 0x39, (byte) 0xe7, (byte) 0x9c, (byte) 0x73, - (byte) 0xce, (byte) 0x39, (byte) 0xe7, (byte) 0x9c, - (byte) 0x73, (byte) 0xce, (byte) 0x39, (byte) 0xe7, - (byte) 0x9c, (byte) 0x73, (byte) 0xce, (byte) 0x39, - (byte) 0xe7, (byte) 0x9c, (byte) 0x73, (byte) 0xce, - (byte) 0x39, (byte) 0xe7, (byte) 0x9c, (byte) 0x73, - (byte) 0xce, (byte) 0x39, (byte) 0xe7, (byte) 0x9c, - (byte) 0x73, (byte) 0xce, (byte) 0x39, (byte) 0xe7, - (byte) 0x9c, (byte) 0x73, (byte) 0xce, (byte) 0x39, - (byte) 0xe7, (byte) 0x9c, (byte) 0x73, (byte) 0xce, - (byte) 0x39, (byte) 0xe7, (byte) 0x9c, (byte) 0x73, - (byte) 0x02, (byte) 0x00, (byte) 0xb1, (byte) 0x2b, - (byte) 0x1c, (byte) 0x00, (byte) 0x76, (byte) 0x22, - (byte) 0x6c, (byte) 0x58, (byte) 0x1d, (byte) 0xe1, - (byte) 0xa4, (byte) 0x68, (byte) 0x2c, (byte) 0xb0, - (byte) 0xd0, (byte) 0x90, (byte) 0x95, (byte) 0x00, - (byte) 0x40, (byte) 0x38, (byte) 0x00, (byte) 0x00, - (byte) 0x60, (byte) 0x8c, (byte) 0x31, (byte) 0xce, - (byte) 0x59, (byte) 0xac, (byte) 0xb5, (byte) 0xd6, - (byte) 0x5a, (byte) 0x2b, (byte) 0xa5, (byte) 0x94, - (byte) 0x92, (byte) 0x50, (byte) 0x6b, (byte) 0xad, - (byte) 0xb5, (byte) 0xd6, (byte) 0x9a, (byte) 0x29, - (byte) 0xa4, (byte) 0x94, (byte) 0x84, (byte) 0x16, - (byte) 0x63, (byte) 0x8c, (byte) 0x31, (byte) 0xc6, - (byte) 0x98, (byte) 0x31, (byte) 0x08, (byte) 0x29, - (byte) 0xb5, (byte) 0x18, (byte) 0x63, (byte) 0x8c, - (byte) 0x31, (byte) 0xc6, (byte) 0x8c, (byte) 0x39, - (byte) 0x47, (byte) 0x2d, (byte) 0xc6, (byte) 0x18, - (byte) 0x63, (byte) 0x8c, (byte) 0x31, (byte) 0xb6, - (byte) 0x56, (byte) 0x4a, (byte) 0x6c, (byte) 0x31, - (byte) 0xc6, (byte) 0x18, (byte) 0x63, (byte) 0x8c, - (byte) 0xb1, (byte) 0xb5, (byte) 0x52, (byte) 0x62, - (byte) 0x8c, (byte) 0x31, (byte) 0xc6, (byte) 0x18, - (byte) 0x63, (byte) 0x8c, (byte) 0x31, (byte) 0xc6, - (byte) 0x16, (byte) 0x5b, (byte) 0x8c, (byte) 0x31, - (byte) 0xc6, (byte) 0x18, (byte) 0x63, (byte) 0x8c, - (byte) 0x31, (byte) 0xb6, (byte) 0x18, (byte) 0x63, - (byte) 0x8c, (byte) 0x31, (byte) 0xc6, (byte) 0x18, - (byte) 0x63, (byte) 0x8c, (byte) 0x31, (byte) 0xc6, - (byte) 0x18, (byte) 0x63, (byte) 0x8c, (byte) 0x31, - (byte) 0xc6, (byte) 0x18, (byte) 0x63, (byte) 0x8c, - (byte) 0x31, (byte) 0xb6, (byte) 0x18, (byte) 0x63, - (byte) 0x8c, (byte) 0x31, (byte) 0xc6, (byte) 0x18, - (byte) 0x63, (byte) 0x8c, (byte) 0x31, (byte) 0xc6, - (byte) 0x18, (byte) 0x63, (byte) 0x8c, (byte) 0x31, - (byte) 0xc6, (byte) 0x18, (byte) 0x63, (byte) 0x8c, - (byte) 0x31, (byte) 0xc6, (byte) 0x18, (byte) 0x63, - (byte) 0x8c, (byte) 0x31, (byte) 0xc6, (byte) 0x18, - (byte) 0x63, (byte) 0x6c, (byte) 0x31, (byte) 0xc6, - (byte) 0x18, (byte) 0x63, (byte) 0x8c, (byte) 0x31, - (byte) 0xc6, (byte) 0x18, (byte) 0x63, (byte) 0x8c, - (byte) 0x31, (byte) 0xc6, (byte) 0x18, (byte) 0x63, - (byte) 0x2c, (byte) 0x00, (byte) 0xc0, (byte) 0xe4, - (byte) 0xc1, (byte) 0x01, (byte) 0x00, (byte) 0x2a, - (byte) 0xc1, (byte) 0xc6, (byte) 0x19, (byte) 0x56, - (byte) 0x92, (byte) 0xce, (byte) 0x0a, (byte) 0x47, - (byte) 0x83, (byte) 0x0b, (byte) 0x0d, (byte) 0x59, - (byte) 0x09, (byte) 0x00, (byte) 0xe4, (byte) 0x06, - (byte) 0x00, (byte) 0x00, (byte) 0xc6, (byte) 0x28, - (byte) 0xc5, (byte) 0x98, (byte) 0x63, (byte) 0xce, - (byte) 0x41, (byte) 0x08, (byte) 0xa1, (byte) 0x94, - (byte) 0x12, (byte) 0x4a, (byte) 0x49, (byte) 0xad, - (byte) 0x75, (byte) 0xce, (byte) 0x39, (byte) 0x08, - (byte) 0x21, (byte) 0x94, (byte) 0x52, (byte) 0x4a, - (byte) 0x49, (byte) 0xa9, (byte) 0xb4, (byte) 0x94, - (byte) 0x62, (byte) 0xca, (byte) 0x98, (byte) 0x73, - (byte) 0xce, (byte) 0x41, (byte) 0x08, (byte) 0xa5, - (byte) 0x94, (byte) 0x12, (byte) 0x4a, (byte) 0x49, - (byte) 0xa9, (byte) 0xa5, (byte) 0xd4, (byte) 0x39, - (byte) 0xe7, (byte) 0x20, (byte) 0x94, (byte) 0x52, - (byte) 0x4a, (byte) 0x4a, (byte) 0x29, (byte) 0xa5, - (byte) 0x94, (byte) 0x5a, (byte) 0x6a, (byte) 0xad, - (byte) 0x73, (byte) 0x10, (byte) 0x42, (byte) 0x08, - (byte) 0xa5, (byte) 0x94, (byte) 0x52, (byte) 0x4a, - (byte) 0x4a, (byte) 0x29, (byte) 0xa5, (byte) 0xd4, - (byte) 0x52, (byte) 0x08, (byte) 0x21, (byte) 0x94, - (byte) 0x52, (byte) 0x4a, (byte) 0x2a, (byte) 0x29, - (byte) 0xa5, (byte) 0x94, (byte) 0x52, (byte) 0x6b, - (byte) 0xad, (byte) 0xa5, (byte) 0x10, (byte) 0x42, - (byte) 0x28, (byte) 0xa5, (byte) 0x94, (byte) 0x94, - (byte) 0x52, (byte) 0x4a, (byte) 0x29, (byte) 0xa5, - (byte) 0xd4, (byte) 0x5a, (byte) 0x8b, (byte) 0xa1, - (byte) 0x94, (byte) 0x90, (byte) 0x4a, (byte) 0x29, - (byte) 0x25, (byte) 0xa5, (byte) 0x94, (byte) 0x52, - (byte) 0x49, (byte) 0x2d, (byte) 0xb5, (byte) 0x96, - (byte) 0x5a, (byte) 0x2a, (byte) 0xa1, (byte) 0x94, - (byte) 0x54, (byte) 0x52, (byte) 0x4a, (byte) 0x29, - (byte) 0xa5, (byte) 0x94, (byte) 0x52, (byte) 0x6b, - (byte) 0xa9, (byte) 0xb5, (byte) 0x56, (byte) 0x4a, - (byte) 0x49, (byte) 0x25, (byte) 0xa5, (byte) 0x94, - (byte) 0x52, (byte) 0x4a, (byte) 0x29, (byte) 0xa5, - (byte) 0xd4, (byte) 0x62, (byte) 0x6b, (byte) 0x29, - (byte) 0x94, (byte) 0x92, (byte) 0x52, (byte) 0x49, - (byte) 0x29, (byte) 0xb5, (byte) 0x94, (byte) 0x52, - (byte) 0x4a, (byte) 0xad, (byte) 0xc5, (byte) 0xd8, - (byte) 0x62, (byte) 0x29, (byte) 0xad, (byte) 0xa4, - (byte) 0x94, (byte) 0x52, (byte) 0x4a, (byte) 0x29, - (byte) 0xa5, (byte) 0xd6, (byte) 0x52, (byte) 0x6c, - (byte) 0xad, (byte) 0xb5, (byte) 0xd8, (byte) 0x52, - (byte) 0x4a, (byte) 0x29, (byte) 0xa5, (byte) 0x96, - (byte) 0x5a, (byte) 0x4a, (byte) 0x29, (byte) 0xb5, - (byte) 0x16, (byte) 0x5b, (byte) 0x6a, (byte) 0x2d, - (byte) 0xa5, (byte) 0x94, (byte) 0x52, (byte) 0x4b, - (byte) 0x29, (byte) 0xa5, (byte) 0x96, (byte) 0x52, - (byte) 0x4b, (byte) 0x2d, (byte) 0xc6, (byte) 0xd6, - (byte) 0x5a, (byte) 0x4b, (byte) 0x29, (byte) 0xa5, - (byte) 0xd4, (byte) 0x52, (byte) 0x6a, (byte) 0xa9, - (byte) 0xa5, (byte) 0x94, (byte) 0x52, (byte) 0x6c, - (byte) 0xad, (byte) 0xb5, (byte) 0x98, (byte) 0x52, - (byte) 0x6a, (byte) 0x2d, (byte) 0xa5, (byte) 0xd4, - (byte) 0x52, (byte) 0x6b, (byte) 0x2d, (byte) 0xb5, - (byte) 0xd8, (byte) 0x52, (byte) 0x6a, (byte) 0x2d, - (byte) 0xb5, (byte) 0x94, (byte) 0x52, (byte) 0x6b, - (byte) 0xa9, (byte) 0xa5, (byte) 0x94, (byte) 0x5a, - (byte) 0x6b, (byte) 0x2d, (byte) 0xb6, (byte) 0xd8, - (byte) 0x5a, (byte) 0x6b, (byte) 0x29, (byte) 0xb5, - (byte) 0x94, (byte) 0x52, (byte) 0x4a, (byte) 0xa9, - (byte) 0xb5, (byte) 0x16, (byte) 0x5b, (byte) 0x8a, - (byte) 0xb1, (byte) 0xb5, (byte) 0xd4, (byte) 0x4a, - (byte) 0x4a, (byte) 0x29, (byte) 0xb5, (byte) 0xd4, - (byte) 0x5a, (byte) 0x6a, (byte) 0x2d, (byte) 0xb6, - (byte) 0x16, (byte) 0x5b, (byte) 0x6b, (byte) 0xad, - (byte) 0xa5, (byte) 0xd6, (byte) 0x5a, (byte) 0x6a, - (byte) 0x29, (byte) 0xa5, (byte) 0x16, (byte) 0x5b, - (byte) 0x8c, (byte) 0x31, (byte) 0xc6, (byte) 0x16, - (byte) 0x63, (byte) 0x6b, (byte) 0x31, (byte) 0xa5, - (byte) 0x94, (byte) 0x52, (byte) 0x4b, (byte) 0xa9, - (byte) 0xa5, (byte) 0x02, (byte) 0x00, (byte) 0x80, - (byte) 0x0e, (byte) 0x1c, (byte) 0x00, (byte) 0x00, - (byte) 0x02, (byte) 0x8c, (byte) 0xa8, (byte) 0xb4, - (byte) 0x10, (byte) 0x3b, (byte) 0xcd, (byte) 0xb8, - (byte) 0xf2, (byte) 0x08, (byte) 0x1c, (byte) 0x51, - (byte) 0xc8, (byte) 0x30, (byte) 0x01, (byte) 0x15, - (byte) 0x1a, (byte) 0xb2, (byte) 0x12, (byte) 0x00, - (byte) 0x20, (byte) 0x03, (byte) 0x00, (byte) 0x20, - (byte) 0x90, (byte) 0x69, (byte) 0x92, (byte) 0x39, - (byte) 0x49, (byte) 0xa9, (byte) 0x11, (byte) 0x26, - (byte) 0x39, (byte) 0xc5, (byte) 0xa0, (byte) 0x94, - (byte) 0xe6, (byte) 0x9c, (byte) 0x53, (byte) 0x4a, - (byte) 0x29, (byte) 0xa5, (byte) 0x34, (byte) 0x44, - (byte) 0x96, (byte) 0x64, (byte) 0x90, (byte) 0x62, - (byte) 0x50, (byte) 0x1d, (byte) 0x99, (byte) 0x8c, - (byte) 0x39, (byte) 0x49, (byte) 0x39, (byte) 0x43, - (byte) 0xa4, (byte) 0x31, (byte) 0xa4, (byte) 0x20, - (byte) 0xf5, (byte) 0x4c, (byte) 0x91, (byte) 0xc7, - (byte) 0x94, (byte) 0x62, (byte) 0x10, (byte) 0x43, - (byte) 0x48, (byte) 0x2a, (byte) 0x74, (byte) 0x8a, - (byte) 0x39, (byte) 0x6c, (byte) 0x35, (byte) 0xf9, - (byte) 0x58, (byte) 0x42, (byte) 0x07, (byte) 0xb1, - (byte) 0x06, (byte) 0x65, (byte) 0x8c, (byte) 0x70, - (byte) 0x29, (byte) 0xc5, (byte) 0x00, (byte) 0x00, - (byte) 0x00, (byte) 0x08, (byte) 0x02, (byte) 0x00, - (byte) 0x04, (byte) 0x84, (byte) 0x04, (byte) 0x00, - (byte) 0x18, (byte) 0x20, (byte) 0x28, (byte) 0x98, - (byte) 0x01, (byte) 0x00, (byte) 0x06, (byte) 0x07, - (byte) 0x08, (byte) 0x23, (byte) 0x07, (byte) 0x02, - (byte) 0x1d, (byte) 0x01, (byte) 0x04, (byte) 0x0e, - (byte) 0x6d, (byte) 0x00, (byte) 0x80, (byte) 0x81, - (byte) 0x08, (byte) 0x99, (byte) 0x09, (byte) 0x0c, - (byte) 0x0a, (byte) 0xa1, (byte) 0xc1, (byte) 0x41, - (byte) 0x26, (byte) 0x00, (byte) 0x3c, (byte) 0x40, - (byte) 0x44, (byte) 0x48, (byte) 0x05, (byte) 0x00, - (byte) 0x89, (byte) 0x09, (byte) 0x8a, (byte) 0xd2, - (byte) 0x85, (byte) 0x2e, (byte) 0x08, (byte) 0x21, - (byte) 0x82, (byte) 0x74, (byte) 0x11, (byte) 0x64, - (byte) 0xf1, (byte) 0xc0, (byte) 0x85, (byte) 0x13, - (byte) 0x37, (byte) 0x9e, (byte) 0xb8, (byte) 0xe1, - (byte) 0x84, (byte) 0x0e, (byte) 0x6d, (byte) 0x20, - (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, - (byte) 0x00, (byte) 0x20, (byte) 0x00, (byte) 0xf0, - (byte) 0x01, (byte) 0x00, (byte) 0x90, (byte) 0x50, - (byte) 0x00, (byte) 0x11, (byte) 0x11, (byte) 0xd1, - (byte) 0xcc, (byte) 0x55, (byte) 0x58, (byte) 0x5c, - (byte) 0x60, (byte) 0x64, (byte) 0x68, (byte) 0x6c, - (byte) 0x70, (byte) 0x74, (byte) 0x78, (byte) 0x7c, - (byte) 0x80, (byte) 0x84, (byte) 0x08, (byte) 0x00, - (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, - (byte) 0x10, (byte) 0x00, (byte) 0x7c, (byte) 0x00, - (byte) 0x00, (byte) 0x24, (byte) 0x22, (byte) 0x40, - (byte) 0x44, (byte) 0x44, (byte) 0x34, (byte) 0x73, - (byte) 0x15, (byte) 0x16, (byte) 0x17, (byte) 0x18, - (byte) 0x19, (byte) 0x1a, (byte) 0x1b, (byte) 0x1c, - (byte) 0x1d, (byte) 0x1e, (byte) 0x1f, (byte) 0x20, - (byte) 0x21, (byte) 0x01, (byte) 0x00, (byte) 0x80, - (byte) 0x00, (byte) 0x02, (byte) 0x00, (byte) 0x00, - (byte) 0x00, (byte) 0x00, (byte) 0x20, (byte) 0x80, - (byte) 0x00, (byte) 0x04, (byte) 0x04, (byte) 0x04, - (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, - (byte) 0x00, (byte) 0x02, (byte) 0x00, (byte) 0x00, - (byte) 0x00, (byte) 0x04, (byte) 0x04 - }; - -} diff --git a/testutils/src/main/java/com/google/android/exoplayer2/testutil/TestUtil.java b/testutils/src/main/java/com/google/android/exoplayer2/testutil/TestUtil.java index fb50ef131b..ee17068242 100644 --- a/testutils/src/main/java/com/google/android/exoplayer2/testutil/TestUtil.java +++ b/testutils/src/main/java/com/google/android/exoplayer2/testutil/TestUtil.java @@ -18,7 +18,6 @@ package com.google.android.exoplayer2.testutil; import static com.google.common.truth.Truth.assertThat; import static org.junit.Assert.fail; -import android.app.Instrumentation; import android.content.Context; import com.google.android.exoplayer2.C; import com.google.android.exoplayer2.extractor.Extractor; @@ -132,20 +131,10 @@ public class TestUtil { return joined; } - public static byte[] getByteArray(Instrumentation instrumentation, String fileName) - throws IOException { - return getByteArray(instrumentation.getContext(), fileName); - } - public static byte[] getByteArray(Context context, String fileName) throws IOException { return Util.toByteArray(getInputStream(context, fileName)); } - public static InputStream getInputStream(Instrumentation instrumentation, String fileName) - throws IOException { - return getInputStream(instrumentation.getContext(), fileName); - } - public static InputStream getInputStream(Context context, String fileName) throws IOException { return context.getResources().getAssets().open(fileName); } diff --git a/testutils/src/test/AndroidManifest.xml b/testutils/src/test/AndroidManifest.xml new file mode 100644 index 0000000000..9602d01633 --- /dev/null +++ b/testutils/src/test/AndroidManifest.xml @@ -0,0 +1,23 @@ + + + + + + + + diff --git a/testutils/src/test/java/com/google/android/exoplayer2/testutil/FakeAdaptiveDataSetTest.java b/testutils/src/test/java/com/google/android/exoplayer2/testutil/FakeAdaptiveDataSetTest.java new file mode 100644 index 0000000000..7fd84f6287 --- /dev/null +++ b/testutils/src/test/java/com/google/android/exoplayer2/testutil/FakeAdaptiveDataSetTest.java @@ -0,0 +1,147 @@ +/* + * Copyright (C) 2017 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.exoplayer2.testutil; + +import static com.google.common.truth.Truth.assertThat; + +import com.google.android.exoplayer2.C; +import com.google.android.exoplayer2.Format; +import com.google.android.exoplayer2.source.TrackGroup; +import com.google.android.exoplayer2.testutil.FakeDataSet.FakeData; +import com.google.android.exoplayer2.testutil.FakeDataSet.FakeData.Segment; +import com.google.android.exoplayer2.util.MimeTypes; +import java.util.List; +import java.util.Random; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.robolectric.RobolectricTestRunner; + +/** Unit test for {@link FakeAdaptiveDataSet}. */ +@RunWith(RobolectricTestRunner.class) +public final class FakeAdaptiveDataSetTest { + + private static final Format[] TEST_FORMATS = { + Format.createVideoSampleFormat( + null, + MimeTypes.VIDEO_H264, + null, + 1000000, + Format.NO_VALUE, + 1280, + 720, + Format.NO_VALUE, + null, + null), + Format.createVideoSampleFormat( + null, + MimeTypes.VIDEO_H264, + null, + 300000, + Format.NO_VALUE, + 640, + 360, + Format.NO_VALUE, + null, + null) + }; + private static final TrackGroup TRACK_GROUP = new TrackGroup(TEST_FORMATS); + + @Test + public void testAdaptiveDataSet() { + long chunkDuration = 2 * C.MICROS_PER_SECOND; + FakeAdaptiveDataSet dataSet = + new FakeAdaptiveDataSet( + TRACK_GROUP, 10 * C.MICROS_PER_SECOND, chunkDuration, 0.0, new Random(0)); + assertThat(dataSet.getAllData().size()).isEqualTo(TEST_FORMATS.length); + assertThat(dataSet.getUri(0).equals(dataSet.getUri(1))).isFalse(); + assertThat(dataSet.getChunkCount()).isEqualTo(5); + assertThat(dataSet.getChunkIndexByPosition(4 * C.MICROS_PER_SECOND)).isEqualTo(2); + assertThat(dataSet.getChunkIndexByPosition(9 * C.MICROS_PER_SECOND)).isEqualTo(4); + for (int i = 0; i < dataSet.getChunkCount(); i++) { + assertThat(dataSet.getChunkDuration(i)).isEqualTo(chunkDuration); + } + assertChunkData(dataSet, chunkDuration); + } + + @Test + public void testAdaptiveDataSetTrailingSmallChunk() { + long chunkDuration = 3 * C.MICROS_PER_SECOND; + FakeAdaptiveDataSet dataSet = + new FakeAdaptiveDataSet( + TRACK_GROUP, 10 * C.MICROS_PER_SECOND, chunkDuration, 0.0, new Random(0)); + assertThat(dataSet.getAllData().size()).isEqualTo(TEST_FORMATS.length); + assertThat(dataSet.getUri(0).equals(dataSet.getUri(1))).isFalse(); + assertThat(dataSet.getChunkCount()).isEqualTo(4); + assertThat(dataSet.getChunkIndexByPosition(4 * C.MICROS_PER_SECOND)).isEqualTo(1); + assertThat(dataSet.getChunkIndexByPosition(9 * C.MICROS_PER_SECOND)).isEqualTo(3); + for (int i = 0; i < dataSet.getChunkCount() - 1; i++) { + assertThat(dataSet.getChunkDuration(i)).isEqualTo(chunkDuration); + } + assertThat(dataSet.getChunkDuration(3)).isEqualTo(1 * C.MICROS_PER_SECOND); + assertChunkData(dataSet, chunkDuration); + } + + @Test + public void testAdaptiveDataSetChunkSizeDistribution() { + double expectedStdDev = 4.0; + FakeAdaptiveDataSet dataSet = + new FakeAdaptiveDataSet( + TRACK_GROUP, + 100000 * C.MICROS_PER_SECOND, + 1 * C.MICROS_PER_SECOND, + expectedStdDev, + new Random(0)); + for (int i = 0; i < TEST_FORMATS.length; i++) { + FakeData data = dataSet.getData(dataSet.getUri(i)); + double mean = computeSegmentSizeMean(data.getSegments()); + double stddev = computeSegmentSizeStdDev(data.getSegments(), mean); + double relativePercentStdDev = stddev / mean * 100.0; + assertThat(relativePercentStdDev).isWithin(0.02).of(expectedStdDev); + assertThat(mean * 8 / TEST_FORMATS[i].bitrate).isWithin(0.01).of(1.0); + } + } + + private void assertChunkData(FakeAdaptiveDataSet dataSet, long chunkDuration) { + for (int i = 0; i < dataSet.getChunkCount(); i++) { + assertThat(dataSet.getStartTime(i)).isEqualTo(chunkDuration * i); + } + for (int s = 0; s < TEST_FORMATS.length; s++) { + FakeData data = dataSet.getData(dataSet.getUri(s)); + assertThat(data.getSegments().size()).isEqualTo(dataSet.getChunkCount()); + for (int i = 0; i < data.getSegments().size(); i++) { + long expectedLength = + TEST_FORMATS[s].bitrate * dataSet.getChunkDuration(i) / (8 * C.MICROS_PER_SECOND); + assertThat(data.getSegments().get(i).length).isEqualTo(expectedLength); + } + } + } + + private static double computeSegmentSizeMean(List segments) { + double totalSize = 0.0; + for (Segment segment : segments) { + totalSize += segment.length; + } + return totalSize / segments.size(); + } + + private static double computeSegmentSizeStdDev(List segments, double mean) { + double totalSquaredSize = 0.0; + for (Segment segment : segments) { + totalSquaredSize += (double) segment.length * segment.length; + } + return Math.sqrt(totalSquaredSize / segments.size() - mean * mean); + } +} diff --git a/testutils/src/test/java/com/google/android/exoplayer2/testutil/FakeClockTest.java b/testutils/src/test/java/com/google/android/exoplayer2/testutil/FakeClockTest.java new file mode 100644 index 0000000000..725753ce46 --- /dev/null +++ b/testutils/src/test/java/com/google/android/exoplayer2/testutil/FakeClockTest.java @@ -0,0 +1,201 @@ +/* + * Copyright (C) 2017 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.exoplayer2.testutil; + +import static com.google.common.truth.Truth.assertThat; + +import android.os.ConditionVariable; +import android.os.HandlerThread; +import com.google.android.exoplayer2.util.Clock; +import com.google.android.exoplayer2.util.HandlerWrapper; +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.TimeUnit; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.robolectric.RobolectricTestRunner; +import org.robolectric.annotation.Config; + +/** Unit test for {@link FakeClock}. */ +@RunWith(RobolectricTestRunner.class) +@Config(shadows = {RobolectricUtil.CustomLooper.class, RobolectricUtil.CustomMessageQueue.class}) +public final class FakeClockTest { + + private static final long TIMEOUT_MS = 10000; + + @Test + public void testAdvanceTime() { + FakeClock fakeClock = new FakeClock(2000); + assertThat(fakeClock.elapsedRealtime()).isEqualTo(2000); + fakeClock.advanceTime(500); + assertThat(fakeClock.elapsedRealtime()).isEqualTo(2500); + fakeClock.advanceTime(0); + assertThat(fakeClock.elapsedRealtime()).isEqualTo(2500); + } + + @Test + public void testSleep() throws InterruptedException { + FakeClock fakeClock = new FakeClock(0); + SleeperThread sleeperThread = new SleeperThread(fakeClock, 1000); + sleeperThread.start(); + assertThat(sleeperThread.waitUntilAsleep(TIMEOUT_MS)).isTrue(); + assertThat(sleeperThread.isSleeping()).isTrue(); + fakeClock.advanceTime(1000); + sleeperThread.join(TIMEOUT_MS); + assertThat(sleeperThread.isSleeping()).isFalse(); + + sleeperThread = new SleeperThread(fakeClock, 0); + sleeperThread.start(); + sleeperThread.join(); + assertThat(sleeperThread.isSleeping()).isFalse(); + + SleeperThread[] sleeperThreads = new SleeperThread[5]; + sleeperThreads[0] = new SleeperThread(fakeClock, 1000); + sleeperThreads[1] = new SleeperThread(fakeClock, 1000); + sleeperThreads[2] = new SleeperThread(fakeClock, 2000); + sleeperThreads[3] = new SleeperThread(fakeClock, 3000); + sleeperThreads[4] = new SleeperThread(fakeClock, 4000); + for (SleeperThread thread : sleeperThreads) { + thread.start(); + assertThat(thread.waitUntilAsleep(TIMEOUT_MS)).isTrue(); + } + assertSleepingStates(new boolean[] {true, true, true, true, true}, sleeperThreads); + fakeClock.advanceTime(1500); + assertThat(sleeperThreads[0].waitUntilAwake(TIMEOUT_MS)).isTrue(); + assertThat(sleeperThreads[1].waitUntilAwake(TIMEOUT_MS)).isTrue(); + assertSleepingStates(new boolean[] {false, false, true, true, true}, sleeperThreads); + fakeClock.advanceTime(2000); + assertThat(sleeperThreads[2].waitUntilAwake(TIMEOUT_MS)).isTrue(); + assertThat(sleeperThreads[3].waitUntilAwake(TIMEOUT_MS)).isTrue(); + assertSleepingStates(new boolean[] {false, false, false, false, true}, sleeperThreads); + fakeClock.advanceTime(2000); + for (SleeperThread thread : sleeperThreads) { + thread.join(TIMEOUT_MS); + } + assertSleepingStates(new boolean[] {false, false, false, false, false}, sleeperThreads); + } + + @Test + public void testPostDelayed() { + HandlerThread handlerThread = new HandlerThread("FakeClockTest thread"); + handlerThread.start(); + FakeClock fakeClock = new FakeClock(0); + HandlerWrapper handler = + fakeClock.createHandler(handlerThread.getLooper(), /* callback= */ null); + + TestRunnable[] testRunnables = { + new TestRunnable(), + new TestRunnable(), + new TestRunnable(), + new TestRunnable(), + new TestRunnable() + }; + handler.postDelayed(testRunnables[0], 0); + handler.postDelayed(testRunnables[1], 100); + handler.postDelayed(testRunnables[2], 200); + waitForHandler(handler); + assertTestRunnableStates(new boolean[] {true, false, false, false, false}, testRunnables); + + fakeClock.advanceTime(150); + handler.postDelayed(testRunnables[3], 50); + handler.postDelayed(testRunnables[4], 100); + waitForHandler(handler); + assertTestRunnableStates(new boolean[] {true, true, false, false, false}, testRunnables); + + fakeClock.advanceTime(50); + waitForHandler(handler); + assertTestRunnableStates(new boolean[] {true, true, true, true, false}, testRunnables); + + fakeClock.advanceTime(1000); + waitForHandler(handler); + assertTestRunnableStates(new boolean[] {true, true, true, true, true}, testRunnables); + } + + private static void assertSleepingStates(boolean[] states, SleeperThread[] sleeperThreads) { + for (int i = 0; i < sleeperThreads.length; i++) { + assertThat(sleeperThreads[i].isSleeping()).isEqualTo(states[i]); + } + } + + private static void waitForHandler(HandlerWrapper handler) { + final ConditionVariable handlerFinished = new ConditionVariable(); + handler.post( + new Runnable() { + @Override + public void run() { + handlerFinished.open(); + } + }); + handlerFinished.block(); + } + + private static void assertTestRunnableStates(boolean[] states, TestRunnable[] testRunnables) { + for (int i = 0; i < testRunnables.length; i++) { + assertThat(testRunnables[i].hasRun).isEqualTo(states[i]); + } + } + + private static final class SleeperThread extends Thread { + + private final Clock clock; + private final long sleepDurationMs; + private final CountDownLatch fallAsleepCountDownLatch; + private final CountDownLatch wakeUpCountDownLatch; + + private volatile boolean isSleeping; + + public SleeperThread(Clock clock, long sleepDurationMs) { + this.clock = clock; + this.sleepDurationMs = sleepDurationMs; + this.fallAsleepCountDownLatch = new CountDownLatch(1); + this.wakeUpCountDownLatch = new CountDownLatch(1); + } + + public boolean waitUntilAsleep(long timeoutMs) throws InterruptedException { + return fallAsleepCountDownLatch.await(timeoutMs, TimeUnit.MILLISECONDS); + } + + public boolean waitUntilAwake(long timeoutMs) throws InterruptedException { + return wakeUpCountDownLatch.await(timeoutMs, TimeUnit.MILLISECONDS); + } + + public boolean isSleeping() { + return isSleeping; + } + + @Override + public void run() { + // This relies on the FakeClock's methods synchronizing on its own monitor to ensure that + // any interactions with it occur only after sleep() has called wait() or returned. + synchronized (clock) { + isSleeping = true; + fallAsleepCountDownLatch.countDown(); + clock.sleep(sleepDurationMs); + isSleeping = false; + wakeUpCountDownLatch.countDown(); + } + } + } + + private static final class TestRunnable implements Runnable { + + public boolean hasRun; + + @Override + public void run() { + hasRun = true; + } + } +} diff --git a/testutils/src/test/java/com/google/android/exoplayer2/testutil/FakeDataSetTest.java b/testutils/src/test/java/com/google/android/exoplayer2/testutil/FakeDataSetTest.java new file mode 100644 index 0000000000..75c6f886c2 --- /dev/null +++ b/testutils/src/test/java/com/google/android/exoplayer2/testutil/FakeDataSetTest.java @@ -0,0 +1,119 @@ +/* + * Copyright (C) 2017 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.exoplayer2.testutil; + +import static com.google.common.truth.Truth.assertThat; + +import android.net.Uri; +import com.google.android.exoplayer2.testutil.FakeDataSet.FakeData.Segment; +import java.io.IOException; +import java.util.List; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.robolectric.RobolectricTestRunner; + +/** Unit test for {@link FakeDataSet} */ +@RunWith(RobolectricTestRunner.class) +public final class FakeDataSetTest { + + @Test + public void testMultipleDataSets() { + byte[][] testData = new byte[4][]; + Uri[] uris = new Uri[3]; + for (int i = 0; i < 4; i++) { + testData[i] = TestUtil.buildTestData(10, i); + if (i > 0) { + uris[i - 1] = Uri.parse("test_uri_" + i); + } + } + FakeDataSet fakeDataSet = + new FakeDataSet() + .newDefaultData() + .appendReadData(testData[0]) + .endData() + .setData(uris[0], testData[1]) + .newData(uris[1]) + .appendReadData(testData[2]) + .endData() + .setData(uris[2], testData[3]); + + assertThat(fakeDataSet.getAllData().size()).isEqualTo(4); + assertThat(fakeDataSet.getData("unseen_uri")).isEqualTo(fakeDataSet.getData((Uri) null)); + for (int i = 0; i < 3; i++) { + assertThat(fakeDataSet.getData(uris[i]).uri).isEqualTo(uris[i]); + } + assertThat(fakeDataSet.getData((Uri) null).getData()).isEqualTo(testData[0]); + for (int i = 1; i < 4; i++) { + assertThat(fakeDataSet.getData(uris[i - 1]).getData()).isEqualTo(testData[i]); + } + } + + @Test + public void testSegmentTypes() { + byte[] testData = TestUtil.buildTestData(3); + Runnable runnable = + new Runnable() { + @Override + public void run() { + // Do nothing. + } + }; + IOException exception = new IOException(); + FakeDataSet fakeDataSet = + new FakeDataSet() + .newDefaultData() + .appendReadData(testData) + .appendReadData(testData) + .appendReadData(50) + .appendReadAction(runnable) + .appendReadError(exception) + .endData(); + + List segments = fakeDataSet.getData((Uri) null).getSegments(); + assertThat(segments.size()).isEqualTo(5); + assertSegment(segments.get(0), testData, 3, 0, null, null); + assertSegment(segments.get(1), testData, 3, 3, null, null); + assertSegment(segments.get(2), null, 50, 6, null, null); + assertSegment(segments.get(3), null, 0, 56, runnable, null); + assertSegment(segments.get(4), null, 0, 56, null, exception); + + byte[] allData = new byte[6]; + System.arraycopy(testData, 0, allData, 0, 3); + System.arraycopy(testData, 0, allData, 3, 3); + assertThat(fakeDataSet.getData((Uri) null).getData()).isEqualTo(allData); + } + + private static void assertSegment( + Segment segment, + byte[] data, + int length, + long byteOffset, + Runnable runnable, + IOException exception) { + if (data != null) { + assertThat(segment.data).isEqualTo(data); + assertThat(data).hasLength(length); + } else { + assertThat(segment.data).isNull(); + } + assertThat(segment.length).isEqualTo(length); + assertThat(segment.byteOffset).isEqualTo(byteOffset); + assertThat(segment.action).isEqualTo(runnable); + assertThat(segment.isActionSegment()).isEqualTo(runnable != null); + assertThat(segment.exception).isEqualTo(exception); + assertThat(segment.isErrorSegment()).isEqualTo(exception != null); + } +} diff --git a/testutils/src/test/java/com/google/android/exoplayer2/testutil/FakeDataSourceTest.java b/testutils/src/test/java/com/google/android/exoplayer2/testutil/FakeDataSourceTest.java new file mode 100644 index 0000000000..c88aba4e08 --- /dev/null +++ b/testutils/src/test/java/com/google/android/exoplayer2/testutil/FakeDataSourceTest.java @@ -0,0 +1,251 @@ +/* + * Copyright (C) 2017 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.exoplayer2.testutil; + +import static com.google.common.truth.Truth.assertThat; +import static org.junit.Assert.fail; + +import android.net.Uri; +import com.google.android.exoplayer2.C; +import com.google.android.exoplayer2.upstream.DataSpec; +import java.io.IOException; +import java.util.Arrays; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.robolectric.RobolectricTestRunner; + +/** Unit test for {@link FakeDataSource}. */ +@RunWith(RobolectricTestRunner.class) +public final class FakeDataSourceTest { + + private static final String URI_STRING = "test://test.test"; + private static final byte[] BUFFER = new byte[500]; + private static final byte[] TEST_DATA = TestUtil.buildTestData(15); + private static final byte[] TEST_DATA_PART_1 = Arrays.copyOf(TEST_DATA, 10); + private static final byte[] TEST_DATA_PART_2 = Arrays.copyOfRange(TEST_DATA, 10, 15); + + private static Uri uri; + private static FakeDataSet fakeDataSet; + + @Before + public void setUp() { + uri = Uri.parse(URI_STRING); + fakeDataSet = + new FakeDataSet() + .newData(uri.toString()) + .appendReadData(TEST_DATA_PART_1) + .appendReadData(TEST_DATA_PART_2) + .endData(); + } + + @Test + public void testReadFull() throws IOException { + FakeDataSource dataSource = new FakeDataSource(fakeDataSet); + assertThat(dataSource.open(new DataSpec(uri))).isEqualTo(15); + assertThat(dataSource.read(BUFFER, 0, BUFFER.length)).isEqualTo(10); + assertBuffer(TEST_DATA_PART_1); + assertThat(dataSource.read(BUFFER, 10, BUFFER.length)).isEqualTo(5); + assertBuffer(TEST_DATA); + assertThat(dataSource.read(BUFFER, 15, BUFFER.length)).isEqualTo(C.RESULT_END_OF_INPUT); + assertBuffer(TEST_DATA); + assertThat(dataSource.read(BUFFER, 20, BUFFER.length)).isEqualTo(C.RESULT_END_OF_INPUT); + dataSource.close(); + } + + @Test + public void testReadPartialOpenEnded() throws IOException { + FakeDataSource dataSource = new FakeDataSource(fakeDataSet); + assertThat(dataSource.open(new DataSpec(uri, 7, C.LENGTH_UNSET, null))).isEqualTo(8); + assertThat(dataSource.read(BUFFER, 0, BUFFER.length)).isEqualTo(3); + assertBuffer(TEST_DATA_PART_1, 7, 3); + assertThat(dataSource.read(BUFFER, 0, BUFFER.length)).isEqualTo(5); + assertBuffer(TEST_DATA_PART_2); + assertThat(dataSource.read(BUFFER, 15, BUFFER.length)).isEqualTo(C.RESULT_END_OF_INPUT); + dataSource.close(); + } + + @Test + public void testReadPartialBounded() throws IOException { + FakeDataSource dataSource = new FakeDataSource(fakeDataSet); + assertThat(dataSource.open(new DataSpec(uri, 9, 3, null))).isEqualTo(3); + assertThat(dataSource.read(BUFFER, 0, BUFFER.length)).isEqualTo(1); + assertBuffer(TEST_DATA_PART_1, 9, 1); + assertThat(dataSource.read(BUFFER, 0, BUFFER.length)).isEqualTo(2); + assertBuffer(TEST_DATA_PART_2, 0, 2); + assertThat(dataSource.read(BUFFER, 0, BUFFER.length)).isEqualTo(C.RESULT_END_OF_INPUT); + dataSource.close(); + + assertThat(dataSource.open(new DataSpec(uri, 11, 4, null))).isEqualTo(4); + assertThat(dataSource.read(BUFFER, 0, BUFFER.length)).isEqualTo(4); + assertBuffer(TEST_DATA_PART_2, 1, 4); + assertThat(dataSource.read(BUFFER, 0, BUFFER.length)).isEqualTo(C.RESULT_END_OF_INPUT); + dataSource.close(); + } + + @Test + public void testDummyData() throws IOException { + FakeDataSource dataSource = + new FakeDataSource( + new FakeDataSet() + .newData(uri.toString()) + .appendReadData(100) + .appendReadData(TEST_DATA) + .appendReadData(200) + .endData()); + assertThat(dataSource.open(new DataSpec(uri))).isEqualTo(315); + assertThat(dataSource.read(BUFFER, 0, BUFFER.length)).isEqualTo(100); + assertThat(dataSource.read(BUFFER, 0, BUFFER.length)).isEqualTo(15); + assertBuffer(TEST_DATA); + assertThat(dataSource.read(BUFFER, 0, BUFFER.length)).isEqualTo(200); + assertThat(dataSource.read(BUFFER, 0, BUFFER.length)).isEqualTo(C.RESULT_END_OF_INPUT); + dataSource.close(); + } + + @Test + public void testException() throws IOException { + String errorMessage = "error, error, error"; + IOException exception = new IOException(errorMessage); + FakeDataSource dataSource = + new FakeDataSource( + new FakeDataSet() + .newData(uri.toString()) + .appendReadData(TEST_DATA) + .appendReadError(exception) + .appendReadData(TEST_DATA) + .endData()); + assertThat(dataSource.open(new DataSpec(uri))).isEqualTo(30); + assertThat(dataSource.read(BUFFER, 0, BUFFER.length)).isEqualTo(15); + assertBuffer(TEST_DATA); + try { + dataSource.read(BUFFER, 0, BUFFER.length); + fail("IOException expected."); + } catch (IOException e) { + assertThat(e).hasMessageThat().isEqualTo(errorMessage); + } + try { + dataSource.read(BUFFER, 0, BUFFER.length); + fail("IOException expected."); + } catch (IOException e) { + assertThat(e).hasMessageThat().isEqualTo(errorMessage); + } + dataSource.close(); + assertThat(dataSource.open(new DataSpec(uri, 15, 15, null))).isEqualTo(15); + assertThat(dataSource.read(BUFFER, 0, BUFFER.length)).isEqualTo(15); + assertBuffer(TEST_DATA); + assertThat(dataSource.read(BUFFER, 0, BUFFER.length)).isEqualTo(C.RESULT_END_OF_INPUT); + dataSource.close(); + } + + @Test + public void testRunnable() throws IOException { + TestRunnable[] runnables = new TestRunnable[3]; + for (int i = 0; i < 3; i++) { + runnables[i] = new TestRunnable(); + } + FakeDataSource dataSource = + new FakeDataSource( + new FakeDataSet() + .newData(uri.toString()) + .appendReadData(TEST_DATA) + .appendReadAction(runnables[0]) + .appendReadData(TEST_DATA) + .appendReadAction(runnables[1]) + .appendReadAction(runnables[2]) + .appendReadData(TEST_DATA) + .endData()); + assertThat(dataSource.open(new DataSpec(uri))).isEqualTo(45); + assertThat(dataSource.read(BUFFER, 0, BUFFER.length)).isEqualTo(15); + assertBuffer(TEST_DATA); + for (int i = 0; i < 3; i++) { + assertThat(runnables[i].ran).isFalse(); + } + assertThat(dataSource.read(BUFFER, 0, BUFFER.length)).isEqualTo(15); + assertBuffer(TEST_DATA); + assertThat(runnables[0].ran).isTrue(); + assertThat(runnables[1].ran).isFalse(); + assertThat(runnables[2].ran).isFalse(); + assertThat(dataSource.read(BUFFER, 0, BUFFER.length)).isEqualTo(15); + assertBuffer(TEST_DATA); + for (int i = 0; i < 3; i++) { + assertThat(runnables[i].ran).isTrue(); + } + assertThat(dataSource.read(BUFFER, 0, BUFFER.length)).isEqualTo(C.RESULT_END_OF_INPUT); + dataSource.close(); + } + + @Test + public void testOpenSourceFailures() throws IOException { + // Empty data. + FakeDataSource dataSource = + new FakeDataSource(new FakeDataSet().newData(uri.toString()).endData()); + try { + dataSource.open(new DataSpec(uri)); + fail("IOException expected."); + } catch (IOException e) { + // Expected. + } finally { + dataSource.close(); + } + + // Non-existent data + dataSource = new FakeDataSource(new FakeDataSet()); + try { + dataSource.open(new DataSpec(uri)); + fail("IOException expected."); + } catch (IOException e) { + // Expected. + } finally { + dataSource.close(); + } + + // DataSpec out of bounds. + dataSource = + new FakeDataSource( + new FakeDataSet() + .newDefaultData() + .appendReadData(TestUtil.buildTestData(10)) + .endData()); + try { + dataSource.open(new DataSpec(uri, 5, 10, null)); + fail("IOException expected."); + } catch (IOException e) { + // Expected. + } finally { + dataSource.close(); + } + } + + private static void assertBuffer(byte[] expected) { + assertBuffer(expected, 0, expected.length); + } + + private static void assertBuffer(byte[] expected, int expectedStart, int expectedLength) { + for (int i = 0; i < expectedLength; i++) { + assertThat(BUFFER[i]).isEqualTo(expected[i + expectedStart]); + } + } + + private static final class TestRunnable implements Runnable { + + public boolean ran; + + @Override + public void run() { + ran = true; + } + } +} diff --git a/testutils/src/test/resources/robolectric.properties b/testutils/src/test/resources/robolectric.properties new file mode 100644 index 0000000000..2f3210368e --- /dev/null +++ b/testutils/src/test/resources/robolectric.properties @@ -0,0 +1 @@ +manifest=src/test/AndroidManifest.xml diff --git a/testutils_robolectric/build.gradle b/testutils_robolectric/build.gradle new file mode 100644 index 0000000000..c221149c29 --- /dev/null +++ b/testutils_robolectric/build.gradle @@ -0,0 +1,39 @@ +// Copyright (C) 2018 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. +apply from: '../constants.gradle' +apply plugin: 'com.android.library' + +android { + compileSdkVersion project.ext.compileSdkVersion + buildToolsVersion project.ext.buildToolsVersion + + defaultConfig { + minSdkVersion project.ext.minSdkVersion + targetSdkVersion project.ext.targetSdkVersion + } + + lintOptions { + // Truth depends on JUnit, which depends on java.lang.management, which + // is not part of Android. Remove this when JUnit 4.13 or later is used. + // See: https://github.com/junit-team/junit4/pull/1187. + disable 'InvalidPackage' + } +} + +dependencies { + api 'org.robolectric:robolectric:' + robolectricVersion + api project(modulePrefix + 'testutils') + implementation project(modulePrefix + 'library-core') + implementation 'com.android.support:support-annotations:' + supportLibraryVersion +} diff --git a/testutils_robolectric/src/main/AndroidManifest.xml b/testutils_robolectric/src/main/AndroidManifest.xml new file mode 100644 index 0000000000..057caad867 --- /dev/null +++ b/testutils_robolectric/src/main/AndroidManifest.xml @@ -0,0 +1,17 @@ + + + + diff --git a/testutils/src/main/java/com/google/android/exoplayer2/testutil/CacheAsserts.java b/testutils_robolectric/src/main/java/com/google/android/exoplayer2/testutil/CacheAsserts.java similarity index 99% rename from testutils/src/main/java/com/google/android/exoplayer2/testutil/CacheAsserts.java rename to testutils_robolectric/src/main/java/com/google/android/exoplayer2/testutil/CacheAsserts.java index f9ababe389..6d3b15ac7a 100644 --- a/testutils/src/main/java/com/google/android/exoplayer2/testutil/CacheAsserts.java +++ b/testutils_robolectric/src/main/java/com/google/android/exoplayer2/testutil/CacheAsserts.java @@ -29,9 +29,7 @@ import com.google.android.exoplayer2.upstream.cache.CacheUtil; import java.io.IOException; import java.util.ArrayList; -/** - * Assertion methods for {@link Cache}. - */ +/** Assertion methods for {@link Cache}. */ public final class CacheAsserts { /** @@ -135,5 +133,4 @@ public final class CacheAsserts { } private CacheAsserts() {} - } diff --git a/testutils/src/main/java/com/google/android/exoplayer2/testutil/FakeMediaClockRenderer.java b/testutils_robolectric/src/main/java/com/google/android/exoplayer2/testutil/FakeMediaClockRenderer.java similarity index 93% rename from testutils/src/main/java/com/google/android/exoplayer2/testutil/FakeMediaClockRenderer.java rename to testutils_robolectric/src/main/java/com/google/android/exoplayer2/testutil/FakeMediaClockRenderer.java index 4d118f9288..009afd1ff7 100644 --- a/testutils/src/main/java/com/google/android/exoplayer2/testutil/FakeMediaClockRenderer.java +++ b/testutils_robolectric/src/main/java/com/google/android/exoplayer2/testutil/FakeMediaClockRenderer.java @@ -19,9 +19,7 @@ import com.google.android.exoplayer2.Format; import com.google.android.exoplayer2.Renderer; import com.google.android.exoplayer2.util.MediaClock; -/** - * Fake abstract {@link Renderer} which is also a {@link MediaClock}. - */ +/** Fake abstract {@link Renderer} which is also a {@link MediaClock}. */ public abstract class FakeMediaClockRenderer extends FakeRenderer implements MediaClock { public FakeMediaClockRenderer(Format... expectedFormats) { @@ -32,5 +30,4 @@ public abstract class FakeMediaClockRenderer extends FakeRenderer implements Med public MediaClock getMediaClock() { return this; } - } diff --git a/testutils/src/main/java/com/google/android/exoplayer2/testutil/FakeShuffleOrder.java b/testutils_robolectric/src/main/java/com/google/android/exoplayer2/testutil/FakeShuffleOrder.java similarity index 100% rename from testutils/src/main/java/com/google/android/exoplayer2/testutil/FakeShuffleOrder.java rename to testutils_robolectric/src/main/java/com/google/android/exoplayer2/testutil/FakeShuffleOrder.java diff --git a/testutils/src/main/java/com/google/android/exoplayer2/testutil/FakeTrackSelection.java b/testutils_robolectric/src/main/java/com/google/android/exoplayer2/testutil/FakeTrackSelection.java similarity index 94% rename from testutils/src/main/java/com/google/android/exoplayer2/testutil/FakeTrackSelection.java rename to testutils_robolectric/src/main/java/com/google/android/exoplayer2/testutil/FakeTrackSelection.java index f5f1987f31..8ceb5338a6 100644 --- a/testutils/src/main/java/com/google/android/exoplayer2/testutil/FakeTrackSelection.java +++ b/testutils_robolectric/src/main/java/com/google/android/exoplayer2/testutil/FakeTrackSelection.java @@ -25,8 +25,8 @@ import com.google.android.exoplayer2.trackselection.TrackSelection; import java.util.List; /** - * A fake {@link TrackSelection} that only returns 1 fixed track, and allows querying the number - * of calls to its methods. + * A fake {@link TrackSelection} that only returns 1 fixed track, and allows querying the number of + * calls to its methods. */ public final class FakeTrackSelection implements TrackSelection { @@ -118,8 +118,8 @@ public final class FakeTrackSelection implements TrackSelection { } @Override - public void updateSelectedTrack(long playbackPositionUs, long bufferedDurationUs, - long availableDurationUs) { + public void updateSelectedTrack( + long playbackPositionUs, long bufferedDurationUs, long availableDurationUs) { assertThat(isEnabled).isTrue(); } @@ -134,5 +134,4 @@ public final class FakeTrackSelection implements TrackSelection { assertThat(isEnabled).isTrue(); return false; } - } diff --git a/testutils/src/main/java/com/google/android/exoplayer2/testutil/FakeTrackSelector.java b/testutils_robolectric/src/main/java/com/google/android/exoplayer2/testutil/FakeTrackSelector.java similarity index 83% rename from testutils/src/main/java/com/google/android/exoplayer2/testutil/FakeTrackSelector.java rename to testutils_robolectric/src/main/java/com/google/android/exoplayer2/testutil/FakeTrackSelector.java index da9a1a18ad..2daafbbb0b 100644 --- a/testutils/src/main/java/com/google/android/exoplayer2/testutil/FakeTrackSelector.java +++ b/testutils_robolectric/src/main/java/com/google/android/exoplayer2/testutil/FakeTrackSelector.java @@ -25,9 +25,7 @@ import com.google.android.exoplayer2.trackselection.TrackSelection; import java.util.ArrayList; import java.util.List; -/** - * A fake {@link MappingTrackSelector} that returns {@link FakeTrackSelection}s. - */ +/** A fake {@link MappingTrackSelector} that returns {@link FakeTrackSelection}s. */ public class FakeTrackSelector extends MappingTrackSelector { private final List selectedTrackSelections = new ArrayList<>(); @@ -38,17 +36,19 @@ public class FakeTrackSelector extends MappingTrackSelector { } /** - * @param mayReuseTrackSelection Whether this {@link FakeTrackSelector} will reuse - * {@link TrackSelection}s during track selection, when it finds previously-selected track - * selection using the same {@link TrackGroup}. + * @param mayReuseTrackSelection Whether this {@link FakeTrackSelector} will reuse {@link + * TrackSelection}s during track selection, when it finds previously-selected track selection + * using the same {@link TrackGroup}. */ public FakeTrackSelector(boolean mayReuseTrackSelection) { this.mayReuseTrackSelection = mayReuseTrackSelection; } @Override - protected TrackSelection[] selectTracks(RendererCapabilities[] rendererCapabilities, - TrackGroupArray[] rendererTrackGroupArrays, int[][][] rendererFormatSupports) + protected TrackSelection[] selectTracks( + RendererCapabilities[] rendererCapabilities, + TrackGroupArray[] rendererTrackGroupArrays, + int[][][] rendererFormatSupports) throws ExoPlaybackException { List resultList = new ArrayList<>(); for (TrackGroupArray trackGroupArray : rendererTrackGroupArrays) { @@ -76,11 +76,8 @@ public class FakeTrackSelector extends MappingTrackSelector { return trackSelectionForRenderer; } - /** - * Returns list of all {@link FakeTrackSelection}s that this track selector has made so far. - */ + /** Returns list of all {@link FakeTrackSelection}s that this track selector has made so far. */ public List getSelectedTrackSelections() { return selectedTrackSelections; } - } diff --git a/testutils/src/main/java/com/google/android/exoplayer2/testutil/MediaSourceTestRunner.java b/testutils_robolectric/src/main/java/com/google/android/exoplayer2/testutil/MediaSourceTestRunner.java similarity index 85% rename from testutils/src/main/java/com/google/android/exoplayer2/testutil/MediaSourceTestRunner.java rename to testutils_robolectric/src/main/java/com/google/android/exoplayer2/testutil/MediaSourceTestRunner.java index 16389112ca..fbb48c9529 100644 --- a/testutils/src/main/java/com/google/android/exoplayer2/testutil/MediaSourceTestRunner.java +++ b/testutils_robolectric/src/main/java/com/google/android/exoplayer2/testutil/MediaSourceTestRunner.java @@ -37,9 +37,7 @@ import java.util.concurrent.CountDownLatch; import java.util.concurrent.LinkedBlockingDeque; import java.util.concurrent.TimeUnit; -/** - * A runner for {@link MediaSource} tests. - */ +/** A runner for {@link MediaSource} tests. */ public class MediaSourceTestRunner { public static final int TIMEOUT_MS = 10000; @@ -78,18 +76,19 @@ public class MediaSourceTestRunner { public void runOnPlaybackThread(final Runnable runnable) { final Throwable[] throwable = new Throwable[1]; final ConditionVariable finishedCondition = new ConditionVariable(); - playbackHandler.post(new Runnable() { - @Override - public void run() { - try { - runnable.run(); - } catch (Throwable e) { - throwable[0] = e; - } finally { - finishedCondition.open(); - } - } - }); + playbackHandler.post( + new Runnable() { + @Override + public void run() { + try { + runnable.run(); + } catch (Throwable e) { + throwable[0] = e; + } finally { + finishedCondition.open(); + } + } + }); assertThat(finishedCondition.block(TIMEOUT_MS)).isTrue(); if (throwable[0] != null) { Util.sneakyThrow(throwable[0]); @@ -103,20 +102,21 @@ public class MediaSourceTestRunner { */ public Timeline prepareSource() throws IOException { final IOException[] prepareError = new IOException[1]; - runOnPlaybackThread(new Runnable() { - @Override - public void run() { - mediaSource.prepareSource(player, true, mediaSourceListener); - try { - // TODO: This only catches errors that are set synchronously in prepareSource. To capture - // async errors we'll need to poll maybeThrowSourceInfoRefreshError until the first call - // to onSourceInfoRefreshed. - mediaSource.maybeThrowSourceInfoRefreshError(); - } catch (IOException e) { - prepareError[0] = e; - } - } - }); + runOnPlaybackThread( + new Runnable() { + @Override + public void run() { + mediaSource.prepareSource(player, true, mediaSourceListener); + try { + // TODO: This only catches errors that are set synchronously in prepareSource. To + // capture async errors we'll need to poll maybeThrowSourceInfoRefreshError until the + // first call to onSourceInfoRefreshed. + mediaSource.maybeThrowSourceInfoRefreshError(); + } catch (IOException e) { + prepareError[0] = e; + } + } + }); if (prepareError[0] != null) { throw prepareError[0]; } @@ -132,12 +132,13 @@ public class MediaSourceTestRunner { */ public MediaPeriod createPeriod(final MediaPeriodId periodId) { final MediaPeriod[] holder = new MediaPeriod[1]; - runOnPlaybackThread(new Runnable() { - @Override - public void run() { - holder[0] = mediaSource.createPeriod(periodId, allocator); - } - }); + runOnPlaybackThread( + new Runnable() { + @Override + public void run() { + holder[0] = mediaSource.createPeriod(periodId, allocator); + } + }); assertThat(holder[0]).isNotNull(); return holder[0]; } @@ -183,24 +184,24 @@ public class MediaSourceTestRunner { * @param mediaPeriod The {@link MediaPeriod} to release. */ public void releasePeriod(final MediaPeriod mediaPeriod) { - runOnPlaybackThread(new Runnable() { - @Override - public void run() { - mediaSource.releasePeriod(mediaPeriod); - } - }); + runOnPlaybackThread( + new Runnable() { + @Override + public void run() { + mediaSource.releasePeriod(mediaPeriod); + } + }); } - /** - * Calls {@link MediaSource#releaseSource()} on the playback thread. - */ + /** Calls {@link MediaSource#releaseSource()} on the playback thread. */ public void releaseSource() { - runOnPlaybackThread(new Runnable() { - @Override - public void run() { - mediaSource.releaseSource(); - } - }); + runOnPlaybackThread( + new Runnable() { + @Override + public void run() { + mediaSource.releaseSource(); + } + }); } /** @@ -276,9 +277,7 @@ public class MediaSourceTestRunner { releasePeriod(secondMediaPeriod); } - /** - * Releases the runner. Should be called when the runner is no longer required. - */ + /** Releases the runner. Should be called when the runner is no longer required. */ public void release() { playbackThread.quit(); } @@ -290,7 +289,6 @@ public class MediaSourceTestRunner { Assertions.checkState(Looper.myLooper() == playbackThread.getLooper()); timelines.addLast(timeline); } - } private static class EventHandlingExoPlayer extends StubExoPlayer @@ -326,5 +324,4 @@ public class MediaSourceTestRunner { return true; } } - } diff --git a/testutils_robolectric/src/main/java/com/google/android/exoplayer2/testutil/OggTestData.java b/testutils_robolectric/src/main/java/com/google/android/exoplayer2/testutil/OggTestData.java new file mode 100644 index 0000000000..8dd0cd16b1 --- /dev/null +++ b/testutils_robolectric/src/main/java/com/google/android/exoplayer2/testutil/OggTestData.java @@ -0,0 +1,1070 @@ +/* + * Copyright (C) 2016 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.exoplayer2.testutil; + +/** Provides ogg/vorbis test data in bytes for unit tests. */ +public final class OggTestData { + + public static FakeExtractorInput createInput(byte[] data, boolean simulateUnknownLength) { + return new FakeExtractorInput.Builder() + .setData(data) + .setSimulateIOErrors(true) + .setSimulateUnknownLength(simulateUnknownLength) + .setSimulatePartialReads(true) + .build(); + } + + public static byte[] buildOggHeader( + int headerType, long granule, int pageSequenceCounter, int pageSegmentCount) { + return TestUtil.createByteArray( + 0x4F, + 0x67, + 0x67, + 0x53, // Oggs. + 0x00, // Stream revision. + headerType, + (int) (granule) & 0xFF, + (int) (granule >> 8) & 0xFF, + (int) (granule >> 16) & 0xFF, + (int) (granule >> 24) & 0xFF, + (int) (granule >> 32) & 0xFF, + (int) (granule >> 40) & 0xFF, + (int) (granule >> 48) & 0xFF, + (int) (granule >> 56) & 0xFF, + 0x00, // LSB of data serial number. + 0x10, + 0x00, + 0x00, // MSB of data serial number. + (pageSequenceCounter) & 0xFF, + (pageSequenceCounter >> 8) & 0xFF, + (pageSequenceCounter >> 16) & 0xFF, + (pageSequenceCounter >> 24) & 0xFF, + 0x00, // LSB of page checksum. + 0x00, + 0x10, + 0x00, // MSB of page checksum. + pageSegmentCount); + } + + /** + * Returns the initial two pages of bytes which by spec contain the three vorbis header packets: + * identification, comment and setup header. + */ + public static byte[] getVorbisHeaderPages() { + byte[] data = new byte[VORBIS_HEADER_PAGES.length]; + System.arraycopy(VORBIS_HEADER_PAGES, 0, data, 0, VORBIS_HEADER_PAGES.length); + return data; + } + + /** Returns a valid vorbis identification header in bytes. */ + public static byte[] getIdentificationHeaderData() { + int idHeaderStart = 28; + int idHeaderLength = 30; + byte[] idHeaderData = new byte[idHeaderLength]; + System.arraycopy(VORBIS_HEADER_PAGES, idHeaderStart, idHeaderData, 0, idHeaderLength); + return idHeaderData; + } + + /** Returns a valid vorbis comment header with 3 comments including utf8 chars in bytes. */ + public static byte[] getCommentHeaderDataUTF8() { + byte[] commentHeaderData = new byte[COMMENT_HEADER_WITH_UTF8.length]; + System.arraycopy( + COMMENT_HEADER_WITH_UTF8, 0, commentHeaderData, 0, COMMENT_HEADER_WITH_UTF8.length); + return commentHeaderData; + } + + /** Returns a valid vorbis setup header in bytes. */ + public static byte[] getSetupHeaderData() { + int setupHeaderStart = 146; + int setupHeaderLength = VORBIS_HEADER_PAGES.length - setupHeaderStart; + byte[] setupHeaderData = new byte[setupHeaderLength]; + System.arraycopy(VORBIS_HEADER_PAGES, setupHeaderStart, setupHeaderData, 0, setupHeaderLength); + return setupHeaderData; + } + + private static final byte[] COMMENT_HEADER_WITH_UTF8 = { + (byte) 0x03, (byte) 0x76, (byte) 0x6f, (byte) 0x72, // 3, v, o, r, + (byte) 0x62, (byte) 0x69, (byte) 0x73, (byte) 0x2b, // b, i, s, . + (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x58, + (byte) 0x69, (byte) 0x70, (byte) 0x68, (byte) 0x2e, + (byte) 0x4f, (byte) 0x72, (byte) 0x67, (byte) 0x20, + (byte) 0x6c, (byte) 0x69, (byte) 0x62, (byte) 0x56, + (byte) 0x6f, (byte) 0x72, (byte) 0x62, (byte) 0x69, + (byte) 0x73, (byte) 0x20, (byte) 0x49, (byte) 0x20, + (byte) 0x32, (byte) 0x30, (byte) 0x31, (byte) 0x32, + (byte) 0x30, (byte) 0x32, (byte) 0x30, (byte) 0x33, + (byte) 0x20, (byte) 0x28, (byte) 0x4f, (byte) 0x6d, + (byte) 0x6e, (byte) 0x69, (byte) 0x70, (byte) 0x72, + (byte) 0x65, (byte) 0x73, (byte) 0x65, (byte) 0x6e, + (byte) 0x74, (byte) 0x29, (byte) 0x03, (byte) 0x00, + (byte) 0x00, (byte) 0x00, (byte) 0x0a, (byte) 0x00, + (byte) 0x00, (byte) 0x00, (byte) 0x41, (byte) 0x4c, + (byte) 0x42, (byte) 0x55, (byte) 0x4d, (byte) 0x3d, + (byte) 0xc3, (byte) 0xa4, (byte) 0xc3, (byte) 0xb6, + (byte) 0x13, (byte) 0x00, (byte) 0x00, (byte) 0x00, + (byte) 0x54, (byte) 0x49, (byte) 0x54, (byte) 0x4c, + (byte) 0x45, (byte) 0x3d, (byte) 0x41, (byte) 0x20, + (byte) 0x73, (byte) 0x61, (byte) 0x6d, (byte) 0x70, + (byte) 0x6c, (byte) 0x65, (byte) 0x20, (byte) 0x73, + (byte) 0x6f, (byte) 0x6e, (byte) 0x67, (byte) 0x0d, + (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x41, + (byte) 0x52, (byte) 0x54, (byte) 0x49, (byte) 0x53, + (byte) 0x54, (byte) 0x3d, (byte) 0x47, (byte) 0x6f, + (byte) 0x6f, (byte) 0x67, (byte) 0x6c, (byte) 0x65, + (byte) 0x01 + }; + + // two OGG pages with 3 packets (id, comment and setup header) + // length: 3743 bytes + private static final byte[] VORBIS_HEADER_PAGES = { /* capture pattern ogg header 1 */ + (byte) 0x4f, (byte) 0x67, (byte) 0x67, (byte) 0x53, // O,g,g,S : start pos 0 + (byte) 0x00, (byte) 0x02, (byte) 0x00, (byte) 0x00, + (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, + (byte) 0x00, (byte) 0x00, (byte) 0x5e, (byte) 0x5f, + (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, + (byte) 0x00, (byte) 0x00, (byte) 0x83, (byte) 0x36, + (byte) 0xe3, (byte) 0x49, (byte) 0x01, (byte) 0x1e, /* capture pattern vorbis id header */ + (byte) 0x01, (byte) 0x76, (byte) 0x6f, (byte) 0x72, // 1,v,o,r : start pos 28 + (byte) 0x62, (byte) 0x69, (byte) 0x73, (byte) 0x00, // b,i,s,. + (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x02, + (byte) 0x22, (byte) 0x56, (byte) 0x00, (byte) 0x00, + (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff, + (byte) 0x6a, (byte) 0x04, (byte) 0x01, (byte) 0x00, + (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff, /* capture pattern ogg header 2 */ + (byte) 0xa9, (byte) 0x01, (byte) 0x4f, (byte) 0x67, // .,.,O,g : start pos 86 + (byte) 0x67, (byte) 0x53, (byte) 0x00, (byte) 0x00, // g,S,.,. + (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, + (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, + (byte) 0x5e, (byte) 0x5f, (byte) 0x00, (byte) 0x00, + (byte) 0x01, (byte) 0x00, (byte) 0x00, (byte) 0x00, + (byte) 0x69, (byte) 0xf8, (byte) 0xeb, (byte) 0xe1, + (byte) 0x10, (byte) 0x2d, (byte) 0xff, (byte) 0xff, + (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff, + (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff, + (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff, /* capture pattern vorbis comment header*/ + (byte) 0x1b, (byte) 0x03, (byte) 0x76, (byte) 0x6f, // .,3,v,o : start pos 101 + (byte) 0x72, (byte) 0x62, (byte) 0x69, (byte) 0x73, // r,b,i,s + (byte) 0x1d, (byte) 0x00, (byte) 0x00, (byte) 0x00, + (byte) 0x58, (byte) 0x69, (byte) 0x70, (byte) 0x68, + (byte) 0x2e, (byte) 0x4f, (byte) 0x72, (byte) 0x67, + (byte) 0x20, (byte) 0x6c, (byte) 0x69, (byte) 0x62, + (byte) 0x56, (byte) 0x6f, (byte) 0x72, (byte) 0x62, + (byte) 0x69, (byte) 0x73, (byte) 0x20, (byte) 0x49, + (byte) 0x20, (byte) 0x32, (byte) 0x30, (byte) 0x30, + (byte) 0x33, (byte) 0x30, (byte) 0x39, (byte) 0x30, + (byte) 0x39, (byte) 0x00, (byte) 0x00, (byte) 0x00, /* capture pattern vorbis setup header */ + (byte) 0x00, (byte) 0x01, (byte) 0x05, (byte) 0x76, // .,.,5,v : start pos 146 + (byte) 0x6f, (byte) 0x72, (byte) 0x62, (byte) 0x69, // o,r,b,i + (byte) 0x73, (byte) 0x22, (byte) 0x42, (byte) 0x43, // s,. + (byte) 0x56, (byte) 0x01, (byte) 0x00, (byte) 0x40, + (byte) 0x00, (byte) 0x00, (byte) 0x18, (byte) 0x42, + (byte) 0x10, (byte) 0x2a, (byte) 0x05, (byte) 0xad, + (byte) 0x63, (byte) 0x8e, (byte) 0x3a, (byte) 0xc8, + (byte) 0x15, (byte) 0x21, (byte) 0x8c, (byte) 0x19, + (byte) 0xa2, (byte) 0xa0, (byte) 0x42, (byte) 0xca, + (byte) 0x29, (byte) 0xc7, (byte) 0x1d, (byte) 0x42, + (byte) 0xd0, (byte) 0x21, (byte) 0xa3, (byte) 0x24, + (byte) 0x43, (byte) 0x88, (byte) 0x3a, (byte) 0xc6, + (byte) 0x35, (byte) 0xc7, (byte) 0x18, (byte) 0x63, + (byte) 0x47, (byte) 0xb9, (byte) 0x64, (byte) 0x8a, + (byte) 0x42, (byte) 0xc9, (byte) 0x81, (byte) 0xd0, + (byte) 0x90, (byte) 0x55, (byte) 0x00, (byte) 0x00, + (byte) 0x40, (byte) 0x00, (byte) 0x00, (byte) 0xa4, + (byte) 0x1c, (byte) 0x57, (byte) 0x50, (byte) 0x72, + (byte) 0x49, (byte) 0x2d, (byte) 0xe7, (byte) 0x9c, + (byte) 0x73, (byte) 0xa3, (byte) 0x18, (byte) 0x57, + (byte) 0xcc, (byte) 0x71, (byte) 0xe8, (byte) 0x20, + (byte) 0xe7, (byte) 0x9c, (byte) 0x73, (byte) 0xe5, + (byte) 0x20, (byte) 0x67, (byte) 0xcc, (byte) 0x71, + (byte) 0x09, (byte) 0x25, (byte) 0xe7, (byte) 0x9c, + (byte) 0x73, (byte) 0x8e, (byte) 0x39, (byte) 0xe7, + (byte) 0x92, (byte) 0x72, (byte) 0x8e, (byte) 0x31, + (byte) 0xe7, (byte) 0x9c, (byte) 0x73, (byte) 0xa3, + (byte) 0x18, (byte) 0x57, (byte) 0x0e, (byte) 0x72, + (byte) 0x29, (byte) 0x2d, (byte) 0xe7, (byte) 0x9c, + (byte) 0x73, (byte) 0x81, (byte) 0x14, (byte) 0x47, + (byte) 0x8a, (byte) 0x71, (byte) 0xa7, (byte) 0x18, + (byte) 0xe7, (byte) 0x9c, (byte) 0x73, (byte) 0xa4, + (byte) 0x1c, (byte) 0x47, (byte) 0x8a, (byte) 0x71, + (byte) 0xa8, (byte) 0x18, (byte) 0xe7, (byte) 0x9c, + (byte) 0x73, (byte) 0x6d, (byte) 0x31, (byte) 0xb7, + (byte) 0x92, (byte) 0x72, (byte) 0xce, (byte) 0x39, + (byte) 0xe7, (byte) 0x9c, (byte) 0x73, (byte) 0xe6, + (byte) 0x20, (byte) 0x87, (byte) 0x52, (byte) 0x72, + (byte) 0xae, (byte) 0x35, (byte) 0xe7, (byte) 0x9c, + (byte) 0x73, (byte) 0xa4, (byte) 0x18, (byte) 0x67, + (byte) 0x0e, (byte) 0x72, (byte) 0x0b, (byte) 0x25, + (byte) 0xe7, (byte) 0x9c, (byte) 0x73, (byte) 0xc6, + (byte) 0x20, (byte) 0x67, (byte) 0xcc, (byte) 0x71, + (byte) 0xeb, (byte) 0x20, (byte) 0xe7, (byte) 0x9c, + (byte) 0x73, (byte) 0x8c, (byte) 0x35, (byte) 0xb7, + (byte) 0xd4, (byte) 0x72, (byte) 0xce, (byte) 0x39, + (byte) 0xe7, (byte) 0x9c, (byte) 0x73, (byte) 0xce, + (byte) 0x39, (byte) 0xe7, (byte) 0x9c, (byte) 0x73, + (byte) 0xce, (byte) 0x39, (byte) 0xe7, (byte) 0x9c, + (byte) 0x73, (byte) 0x8c, (byte) 0x31, (byte) 0xe7, + (byte) 0x9c, (byte) 0x73, (byte) 0xce, (byte) 0x39, + (byte) 0xe7, (byte) 0x9c, (byte) 0x73, (byte) 0x6e, + (byte) 0x31, (byte) 0xe7, (byte) 0x16, (byte) 0x73, + (byte) 0xae, (byte) 0x39, (byte) 0xe7, (byte) 0x9c, + (byte) 0x73, (byte) 0xce, (byte) 0x39, (byte) 0xe7, + (byte) 0x1c, (byte) 0x73, (byte) 0xce, (byte) 0x39, + (byte) 0xe7, (byte) 0x9c, (byte) 0x73, (byte) 0x20, + (byte) 0x34, (byte) 0x64, (byte) 0x15, (byte) 0x00, + (byte) 0x90, (byte) 0x00, (byte) 0x00, (byte) 0xa0, + (byte) 0xa1, (byte) 0x28, (byte) 0x8a, (byte) 0xe2, + (byte) 0x28, (byte) 0x0e, (byte) 0x10, (byte) 0x1a, + (byte) 0xb2, (byte) 0x0a, (byte) 0x00, (byte) 0xc8, + (byte) 0x00, (byte) 0x00, (byte) 0x10, (byte) 0x40, + (byte) 0x71, (byte) 0x14, (byte) 0x47, (byte) 0x91, + (byte) 0x14, (byte) 0x4b, (byte) 0xb1, (byte) 0x1c, + (byte) 0xcb, (byte) 0xd1, (byte) 0x24, (byte) 0x0d, + (byte) 0x08, (byte) 0x0d, (byte) 0x59, (byte) 0x05, + (byte) 0x00, (byte) 0x00, (byte) 0x01, (byte) 0x00, + (byte) 0x08, (byte) 0x00, (byte) 0x00, (byte) 0xa0, + (byte) 0x48, (byte) 0x86, (byte) 0xa4, (byte) 0x48, + (byte) 0x8a, (byte) 0xa5, (byte) 0x58, (byte) 0x8e, + (byte) 0x66, (byte) 0x69, (byte) 0x9e, (byte) 0x26, + (byte) 0x7a, (byte) 0xa2, (byte) 0x28, (byte) 0x9a, + (byte) 0xa2, (byte) 0x2a, (byte) 0xab, (byte) 0xb2, + (byte) 0x69, (byte) 0xca, (byte) 0xb2, (byte) 0x2c, + (byte) 0xcb, (byte) 0xb2, (byte) 0xeb, (byte) 0xba, + (byte) 0x2e, (byte) 0x10, (byte) 0x1a, (byte) 0xb2, + (byte) 0x0a, (byte) 0x00, (byte) 0x48, (byte) 0x00, + (byte) 0x00, (byte) 0x50, (byte) 0x51, (byte) 0x14, + (byte) 0xc5, (byte) 0x70, (byte) 0x14, (byte) 0x07, + (byte) 0x08, (byte) 0x0d, (byte) 0x59, (byte) 0x05, + (byte) 0x00, (byte) 0x64, (byte) 0x00, (byte) 0x00, + (byte) 0x08, (byte) 0x60, (byte) 0x28, (byte) 0x8a, + (byte) 0xa3, (byte) 0x38, (byte) 0x8e, (byte) 0xe4, + (byte) 0x58, (byte) 0x92, (byte) 0xa5, (byte) 0x59, + (byte) 0x9e, (byte) 0x07, (byte) 0x84, (byte) 0x86, + (byte) 0xac, (byte) 0x02, (byte) 0x00, (byte) 0x80, + (byte) 0x00, (byte) 0x00, (byte) 0x04, (byte) 0x00, + (byte) 0x00, (byte) 0x50, (byte) 0x0c, (byte) 0x47, + (byte) 0xb1, (byte) 0x14, (byte) 0x4d, (byte) 0xf1, + (byte) 0x24, (byte) 0xcf, (byte) 0xf2, (byte) 0x3c, + (byte) 0xcf, (byte) 0xf3, (byte) 0x3c, (byte) 0xcf, + (byte) 0xf3, (byte) 0x3c, (byte) 0xcf, (byte) 0xf3, + (byte) 0x3c, (byte) 0xcf, (byte) 0xf3, (byte) 0x3c, + (byte) 0xcf, (byte) 0xf3, (byte) 0x3c, (byte) 0xcf, + (byte) 0xf3, (byte) 0x3c, (byte) 0x0d, (byte) 0x08, + (byte) 0x0d, (byte) 0x59, (byte) 0x05, (byte) 0x00, + (byte) 0x20, (byte) 0x00, (byte) 0x00, (byte) 0x00, + (byte) 0x82, (byte) 0x28, (byte) 0x64, (byte) 0x18, + (byte) 0x03, (byte) 0x42, (byte) 0x43, (byte) 0x56, + (byte) 0x01, (byte) 0x00, (byte) 0x40, (byte) 0x00, + (byte) 0x00, (byte) 0x08, (byte) 0x21, (byte) 0x1a, + (byte) 0x19, (byte) 0x43, (byte) 0x9d, (byte) 0x52, + (byte) 0x12, (byte) 0x5c, (byte) 0x0a, (byte) 0x16, + (byte) 0x42, (byte) 0x1c, (byte) 0x11, (byte) 0x43, + (byte) 0x1d, (byte) 0x42, (byte) 0xce, (byte) 0x43, + (byte) 0xa9, (byte) 0xa5, (byte) 0x83, (byte) 0xe0, + (byte) 0x29, (byte) 0x85, (byte) 0x25, (byte) 0x63, + (byte) 0xd2, (byte) 0x53, (byte) 0xac, (byte) 0x41, + (byte) 0x08, (byte) 0x21, (byte) 0x7c, (byte) 0xef, + (byte) 0x3d, (byte) 0xf7, (byte) 0xde, (byte) 0x7b, + (byte) 0xef, (byte) 0x81, (byte) 0xd0, (byte) 0x90, + (byte) 0x55, (byte) 0x00, (byte) 0x00, (byte) 0x10, + (byte) 0x00, (byte) 0x00, (byte) 0x61, (byte) 0x14, + (byte) 0x38, (byte) 0x88, (byte) 0x81, (byte) 0xc7, + (byte) 0x24, (byte) 0x08, (byte) 0x21, (byte) 0x84, + (byte) 0x62, (byte) 0x14, (byte) 0x27, (byte) 0x44, + (byte) 0x71, (byte) 0xa6, (byte) 0x20, (byte) 0x08, + (byte) 0x21, (byte) 0x84, (byte) 0xe5, (byte) 0x24, + (byte) 0x58, (byte) 0xca, (byte) 0x79, (byte) 0xe8, + (byte) 0x24, (byte) 0x08, (byte) 0xdd, (byte) 0x83, + (byte) 0x10, (byte) 0x42, (byte) 0xb8, (byte) 0x9c, + (byte) 0x7b, (byte) 0xcb, (byte) 0xb9, (byte) 0xf7, + (byte) 0xde, (byte) 0x7b, (byte) 0x20, (byte) 0x34, + (byte) 0x64, (byte) 0x15, (byte) 0x00, (byte) 0x00, + (byte) 0x08, (byte) 0x00, (byte) 0xc0, (byte) 0x20, + (byte) 0x84, (byte) 0x10, (byte) 0x42, (byte) 0x08, + (byte) 0x21, (byte) 0x84, (byte) 0x10, (byte) 0x42, + (byte) 0x08, (byte) 0x29, (byte) 0xa4, (byte) 0x94, + (byte) 0x52, (byte) 0x48, (byte) 0x29, (byte) 0xa6, + (byte) 0x98, (byte) 0x62, (byte) 0x8a, (byte) 0x29, + (byte) 0xc7, (byte) 0x1c, (byte) 0x73, (byte) 0xcc, + (byte) 0x31, (byte) 0xc7, (byte) 0x20, (byte) 0x83, + (byte) 0x0c, (byte) 0x32, (byte) 0xe8, (byte) 0xa0, + (byte) 0x93, (byte) 0x4e, (byte) 0x3a, (byte) 0xc9, + (byte) 0xa4, (byte) 0x92, (byte) 0x4e, (byte) 0x3a, + (byte) 0xca, (byte) 0x24, (byte) 0xa3, (byte) 0x8e, + (byte) 0x52, (byte) 0x6b, (byte) 0x29, (byte) 0xb5, + (byte) 0x14, (byte) 0x53, (byte) 0x4c, (byte) 0xb1, + (byte) 0xe5, (byte) 0x16, (byte) 0x63, (byte) 0xad, + (byte) 0xb5, (byte) 0xd6, (byte) 0x9c, (byte) 0x73, + (byte) 0xaf, (byte) 0x41, (byte) 0x29, (byte) 0x63, + (byte) 0x8c, (byte) 0x31, (byte) 0xc6, (byte) 0x18, + (byte) 0x63, (byte) 0x8c, (byte) 0x31, (byte) 0xc6, + (byte) 0x18, (byte) 0x63, (byte) 0x8c, (byte) 0x31, + (byte) 0xc6, (byte) 0x18, (byte) 0x23, (byte) 0x08, + (byte) 0x0d, (byte) 0x59, (byte) 0x05, (byte) 0x00, + (byte) 0x80, (byte) 0x00, (byte) 0x00, (byte) 0x10, + (byte) 0x06, (byte) 0x19, (byte) 0x64, (byte) 0x90, + (byte) 0x41, (byte) 0x08, (byte) 0x21, (byte) 0x84, + (byte) 0x14, (byte) 0x52, (byte) 0x48, (byte) 0x29, + (byte) 0xa6, (byte) 0x98, (byte) 0x72, (byte) 0xcc, + (byte) 0x31, (byte) 0xc7, (byte) 0x1c, (byte) 0x03, + (byte) 0x42, (byte) 0x43, (byte) 0x56, (byte) 0x01, + (byte) 0x00, (byte) 0x80, (byte) 0x00, (byte) 0x00, + (byte) 0x02, (byte) 0x00, (byte) 0x00, (byte) 0x00, + (byte) 0x1c, (byte) 0x45, (byte) 0x52, (byte) 0x24, + (byte) 0x47, (byte) 0x72, (byte) 0x24, (byte) 0x47, + (byte) 0x92, (byte) 0x24, (byte) 0xc9, (byte) 0x92, + (byte) 0x2c, (byte) 0x49, (byte) 0x93, (byte) 0x3c, + (byte) 0xcb, (byte) 0xb3, (byte) 0x3c, (byte) 0xcb, + (byte) 0xb3, (byte) 0x3c, (byte) 0x4d, (byte) 0xd4, + (byte) 0x44, (byte) 0x4d, (byte) 0x15, (byte) 0x55, + (byte) 0xd5, (byte) 0x55, (byte) 0x6d, (byte) 0xd7, + (byte) 0xf6, (byte) 0x6d, (byte) 0x5f, (byte) 0xf6, + (byte) 0x6d, (byte) 0xdf, (byte) 0xd5, (byte) 0x65, + (byte) 0xdf, (byte) 0xf6, (byte) 0x65, (byte) 0xdb, + (byte) 0xd5, (byte) 0x65, (byte) 0x5d, (byte) 0x96, + (byte) 0x65, (byte) 0xdd, (byte) 0xb5, (byte) 0x6d, + (byte) 0x5d, (byte) 0xd6, (byte) 0x5d, (byte) 0x5d, + (byte) 0xd7, (byte) 0x75, (byte) 0x5d, (byte) 0xd7, + (byte) 0x75, (byte) 0x5d, (byte) 0xd7, (byte) 0x75, + (byte) 0x5d, (byte) 0xd7, (byte) 0x75, (byte) 0x5d, + (byte) 0xd7, (byte) 0x75, (byte) 0x5d, (byte) 0xd7, + (byte) 0x81, (byte) 0xd0, (byte) 0x90, (byte) 0x55, + (byte) 0x00, (byte) 0x80, (byte) 0x04, (byte) 0x00, + (byte) 0x80, (byte) 0x8e, (byte) 0xe4, (byte) 0x38, + (byte) 0x8e, (byte) 0xe4, (byte) 0x38, (byte) 0x8e, + (byte) 0xe4, (byte) 0x48, (byte) 0x8e, (byte) 0xa4, + (byte) 0x48, (byte) 0x0a, (byte) 0x10, (byte) 0x1a, + (byte) 0xb2, (byte) 0x0a, (byte) 0x00, (byte) 0x90, + (byte) 0x01, (byte) 0x00, (byte) 0x10, (byte) 0x00, + (byte) 0x80, (byte) 0xa3, (byte) 0x38, (byte) 0x8a, + (byte) 0xe3, (byte) 0x48, (byte) 0x8e, (byte) 0xe4, + (byte) 0x58, (byte) 0x8e, (byte) 0x25, (byte) 0x59, + (byte) 0x92, (byte) 0x26, (byte) 0x69, (byte) 0x96, + (byte) 0x67, (byte) 0x79, (byte) 0x96, (byte) 0xa7, + (byte) 0x79, (byte) 0x9a, (byte) 0xa8, (byte) 0x89, + (byte) 0x1e, (byte) 0x10, (byte) 0x1a, (byte) 0xb2, + (byte) 0x0a, (byte) 0x00, (byte) 0x00, (byte) 0x04, + (byte) 0x00, (byte) 0x10, (byte) 0x00, (byte) 0x00, + (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x80, + (byte) 0xa2, (byte) 0x28, (byte) 0x8a, (byte) 0xa3, + (byte) 0x38, (byte) 0x8e, (byte) 0x24, (byte) 0x59, + (byte) 0x96, (byte) 0xa6, (byte) 0x69, (byte) 0x9e, + (byte) 0xa7, (byte) 0x7a, (byte) 0xa2, (byte) 0x28, + (byte) 0x9a, (byte) 0xaa, (byte) 0xaa, (byte) 0x8a, + (byte) 0xa6, (byte) 0xa9, (byte) 0xaa, (byte) 0xaa, + (byte) 0x6a, (byte) 0x9a, (byte) 0xa6, (byte) 0x69, + (byte) 0x9a, (byte) 0xa6, (byte) 0x69, (byte) 0x9a, + (byte) 0xa6, (byte) 0x69, (byte) 0x9a, (byte) 0xa6, + (byte) 0x69, (byte) 0x9a, (byte) 0xa6, (byte) 0x69, + (byte) 0x9a, (byte) 0xa6, (byte) 0x69, (byte) 0x9a, + (byte) 0xa6, (byte) 0x69, (byte) 0x9a, (byte) 0xa6, + (byte) 0x69, (byte) 0x9a, (byte) 0xa6, (byte) 0x69, + (byte) 0x9a, (byte) 0xa6, (byte) 0x69, (byte) 0x9a, + (byte) 0xa6, (byte) 0x69, (byte) 0x02, (byte) 0xa1, + (byte) 0x21, (byte) 0xab, (byte) 0x00, (byte) 0x00, + (byte) 0x09, (byte) 0x00, (byte) 0x00, (byte) 0x1d, + (byte) 0xc7, (byte) 0x71, (byte) 0x1c, (byte) 0x47, + (byte) 0x71, (byte) 0x1c, (byte) 0xc7, (byte) 0x71, + (byte) 0x24, (byte) 0x47, (byte) 0x92, (byte) 0x24, + (byte) 0x20, (byte) 0x34, (byte) 0x64, (byte) 0x15, + (byte) 0x00, (byte) 0x20, (byte) 0x03, (byte) 0x00, + (byte) 0x20, (byte) 0x00, (byte) 0x00, (byte) 0x43, + (byte) 0x51, (byte) 0x1c, (byte) 0x45, (byte) 0x72, + (byte) 0x2c, (byte) 0xc7, (byte) 0x92, (byte) 0x34, + (byte) 0x4b, (byte) 0xb3, (byte) 0x3c, (byte) 0xcb, + (byte) 0xd3, (byte) 0x44, (byte) 0xcf, (byte) 0xf4, + (byte) 0x5c, (byte) 0x51, (byte) 0x36, (byte) 0x75, + (byte) 0x53, (byte) 0x57, (byte) 0x6d, (byte) 0x20, + (byte) 0x34, (byte) 0x64, (byte) 0x15, (byte) 0x00, + (byte) 0x00, (byte) 0x08, (byte) 0x00, (byte) 0x20, + (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, + (byte) 0x00, (byte) 0x00, (byte) 0xc7, (byte) 0x73, + (byte) 0x3c, (byte) 0xc7, (byte) 0x73, (byte) 0x3c, + (byte) 0xc9, (byte) 0x93, (byte) 0x3c, (byte) 0xcb, + (byte) 0x73, (byte) 0x3c, (byte) 0xc7, (byte) 0x93, + (byte) 0x3c, (byte) 0x49, (byte) 0xd3, (byte) 0x34, + (byte) 0x4d, (byte) 0xd3, (byte) 0x34, (byte) 0x4d, + (byte) 0xd3, (byte) 0x34, (byte) 0x4d, (byte) 0xd3, + (byte) 0x34, (byte) 0x4d, (byte) 0xd3, (byte) 0x34, + (byte) 0x4d, (byte) 0xd3, (byte) 0x34, (byte) 0x4d, + (byte) 0xd3, (byte) 0x34, (byte) 0x4d, (byte) 0xd3, + (byte) 0x34, (byte) 0x4d, (byte) 0xd3, (byte) 0x34, + (byte) 0x4d, (byte) 0xd3, (byte) 0x34, (byte) 0x4d, + (byte) 0xd3, (byte) 0x34, (byte) 0x4d, (byte) 0xd3, + (byte) 0x34, (byte) 0x4d, (byte) 0xd3, (byte) 0x34, + (byte) 0x4d, (byte) 0xd3, (byte) 0x34, (byte) 0x4d, + (byte) 0x03, (byte) 0x42, (byte) 0x43, (byte) 0x56, + (byte) 0x02, (byte) 0x00, (byte) 0x64, (byte) 0x00, + (byte) 0x00, (byte) 0x90, (byte) 0x02, (byte) 0xcf, + (byte) 0x42, (byte) 0x29, (byte) 0x2d, (byte) 0x46, + (byte) 0x02, (byte) 0x1c, (byte) 0x88, (byte) 0x98, + (byte) 0xa3, (byte) 0xd8, (byte) 0x7b, (byte) 0xef, + (byte) 0xbd, (byte) 0xf7, (byte) 0xde, (byte) 0x7b, + (byte) 0x65, (byte) 0x3c, (byte) 0x92, (byte) 0x88, + (byte) 0x49, (byte) 0xed, (byte) 0x31, (byte) 0xf4, + (byte) 0xd4, (byte) 0x31, (byte) 0x07, (byte) 0xb1, + (byte) 0x67, (byte) 0xc6, (byte) 0x23, (byte) 0x66, + (byte) 0x94, (byte) 0xa3, (byte) 0xd8, (byte) 0x29, + (byte) 0xcf, (byte) 0x1c, (byte) 0x42, (byte) 0x0c, + (byte) 0x62, (byte) 0xe8, (byte) 0x3c, (byte) 0x74, + (byte) 0x4a, (byte) 0x31, (byte) 0x88, (byte) 0x29, + (byte) 0xf5, (byte) 0x52, (byte) 0x32, (byte) 0xc6, + (byte) 0x20, (byte) 0xc6, (byte) 0xd8, (byte) 0x63, + (byte) 0x0c, (byte) 0x21, (byte) 0x94, (byte) 0x18, + (byte) 0x08, (byte) 0x0d, (byte) 0x59, (byte) 0x21, + (byte) 0x00, (byte) 0x84, (byte) 0x66, (byte) 0x00, + (byte) 0x18, (byte) 0x24, (byte) 0x09, (byte) 0x90, + (byte) 0x34, (byte) 0x0d, (byte) 0x90, (byte) 0x34, + (byte) 0x0d, (byte) 0x00, (byte) 0x00, (byte) 0x00, + (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, + (byte) 0x24, (byte) 0x4f, (byte) 0x03, (byte) 0x34, + (byte) 0x51, (byte) 0x04, (byte) 0x34, (byte) 0x4f, + (byte) 0x04, (byte) 0x00, (byte) 0x00, (byte) 0x00, + (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, + (byte) 0x49, (byte) 0xf3, (byte) 0x00, (byte) 0x4d, + (byte) 0xf4, (byte) 0x00, (byte) 0x4d, (byte) 0x14, + (byte) 0x01, (byte) 0x00, (byte) 0x00, (byte) 0x00, + (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, + (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, + (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, + (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, + (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, + (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, + (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, + (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, + (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, + (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, + (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, + (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, + (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, + (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, + (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, + (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, + (byte) 0x90, (byte) 0x3c, (byte) 0x0d, (byte) 0xf0, + (byte) 0x44, (byte) 0x11, (byte) 0xd0, (byte) 0x44, + (byte) 0x11, (byte) 0x00, (byte) 0x00, (byte) 0x00, + (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, + (byte) 0x34, (byte) 0x51, (byte) 0x04, (byte) 0x44, + (byte) 0x51, (byte) 0x05, (byte) 0x44, (byte) 0xd5, + (byte) 0x04, (byte) 0x00, (byte) 0x00, (byte) 0x00, + (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, + (byte) 0x4d, (byte) 0x14, (byte) 0x01, (byte) 0x4f, + (byte) 0x15, (byte) 0x01, (byte) 0xd1, (byte) 0x54, + (byte) 0x01, (byte) 0x00, (byte) 0x00, (byte) 0x00, + (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, + (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, + (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, + (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, + (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, + (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, + (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, + (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, + (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, + (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, + (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, + (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, + (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, + (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, + (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, + (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, + (byte) 0x90, (byte) 0x34, (byte) 0x0f, (byte) 0xd0, + (byte) 0x44, (byte) 0x11, (byte) 0xf0, (byte) 0x44, + (byte) 0x11, (byte) 0x00, (byte) 0x00, (byte) 0x00, + (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, + (byte) 0x34, (byte) 0x51, (byte) 0x04, (byte) 0x44, + (byte) 0xd5, (byte) 0x04, (byte) 0x3c, (byte) 0x51, + (byte) 0x05, (byte) 0x00, (byte) 0x00, (byte) 0x00, + (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, + (byte) 0x4d, (byte) 0x14, (byte) 0x01, (byte) 0xd1, + (byte) 0x54, (byte) 0x01, (byte) 0x51, (byte) 0x15, + (byte) 0x01, (byte) 0x00, (byte) 0x00, (byte) 0x00, + (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, + (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, + (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, + (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, + (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, + (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, + (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, + (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, + (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, + (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, + (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, + (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, + (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, + (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, + (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, + (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, + (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, + (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, + (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, + (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, + (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, + (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, + (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, + (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, + (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, + (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, + (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, + (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, + (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, + (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, + (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, + (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, + (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, + (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, + (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, + (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, + (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, + (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, + (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, + (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, + (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, + (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, + (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, + (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, + (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, + (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, + (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, + (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, + (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, + (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, + (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, + (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, + (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, + (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, + (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, + (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, + (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, + (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, + (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, + (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, + (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, + (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, + (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, + (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, + (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, + (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, + (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, + (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, + (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, + (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, + (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, + (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, + (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, + (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, + (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, + (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, + (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, + (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, + (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, + (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, + (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, + (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, + (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, + (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, + (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, + (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, + (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, + (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, + (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, + (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, + (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, + (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, + (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, + (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, + (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, + (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, + (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, + (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, + (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, + (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, + (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, + (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, + (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, + (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, + (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, + (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, + (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, + (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, + (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, + (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, + (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, + (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, + (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, + (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, + (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, + (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, + (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, + (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, + (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, + (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, + (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, + (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, + (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, + (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, + (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, + (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, + (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, + (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, + (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, + (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, + (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, + (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, + (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, + (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, + (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, + (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, + (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, + (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, + (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, + (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, + (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, + (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, + (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, + (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, + (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, + (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, + (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, + (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, + (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, + (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, + (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, + (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, + (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x04, + (byte) 0x00, (byte) 0x00, (byte) 0x04, (byte) 0x38, + (byte) 0x00, (byte) 0x00, (byte) 0x04, (byte) 0x58, + (byte) 0x08, (byte) 0x85, (byte) 0x86, (byte) 0xac, + (byte) 0x08, (byte) 0x00, (byte) 0xe2, (byte) 0x04, + (byte) 0x00, (byte) 0x04, (byte) 0x00, (byte) 0x00, + (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, + (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, + (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, + (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, + (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, + (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, + (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, + (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, + (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, + (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, + (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, + (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, + (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, + (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, + (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, + (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, + (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, + (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, + (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x10, + (byte) 0x00, (byte) 0x00, (byte) 0x30, (byte) 0xe0, + (byte) 0x00, (byte) 0x00, (byte) 0x10, (byte) 0x60, + (byte) 0x42, (byte) 0x19, (byte) 0x28, (byte) 0x34, + (byte) 0x64, (byte) 0x45, (byte) 0x00, (byte) 0x10, + (byte) 0x27, (byte) 0x00, (byte) 0x60, (byte) 0x70, + (byte) 0x1c, (byte) 0xcb, (byte) 0x02, (byte) 0x00, + (byte) 0x00, (byte) 0x47, (byte) 0x92, (byte) 0x34, + (byte) 0x0d, (byte) 0x00, (byte) 0x00, (byte) 0x1c, + (byte) 0x49, (byte) 0xd2, (byte) 0x34, (byte) 0x00, + (byte) 0x00, (byte) 0xd0, (byte) 0x34, (byte) 0x4d, + (byte) 0x14, (byte) 0x01, (byte) 0x00, (byte) 0xc0, + (byte) 0xd2, (byte) 0x34, (byte) 0x51, (byte) 0x04, + (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, + (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, + (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, + (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, + (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, + (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, + (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, + (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, + (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, + (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, + (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, + (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, + (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, + (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, + (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, + (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, + (byte) 0x10, (byte) 0x00, (byte) 0x00, (byte) 0x30, + (byte) 0xe0, (byte) 0x00, (byte) 0x00, (byte) 0x10, + (byte) 0x60, (byte) 0x42, (byte) 0x19, (byte) 0x28, + (byte) 0x34, (byte) 0x64, (byte) 0x25, (byte) 0x00, + (byte) 0x10, (byte) 0x05, (byte) 0x00, (byte) 0x60, + (byte) 0x30, (byte) 0x14, (byte) 0x4d, (byte) 0x03, + (byte) 0x58, (byte) 0x16, (byte) 0xc0, (byte) 0xb2, + (byte) 0x00, (byte) 0x9a, (byte) 0x06, (byte) 0xd0, + (byte) 0x34, (byte) 0x80, (byte) 0xe7, (byte) 0x01, + (byte) 0x3c, (byte) 0x11, (byte) 0x60, (byte) 0x9a, + (byte) 0x00, (byte) 0x40, (byte) 0x00, (byte) 0x00, + (byte) 0x40, (byte) 0x81, (byte) 0x03, (byte) 0x00, + (byte) 0x40, (byte) 0x80, (byte) 0x0d, (byte) 0x9a, + (byte) 0x12, (byte) 0x8b, (byte) 0x03, (byte) 0x14, + (byte) 0x1a, (byte) 0xb2, (byte) 0x12, (byte) 0x00, + (byte) 0x88, (byte) 0x02, (byte) 0x00, (byte) 0x30, + (byte) 0x28, (byte) 0x8a, (byte) 0x24, (byte) 0x59, + (byte) 0x96, (byte) 0xe7, (byte) 0x41, (byte) 0xd3, + (byte) 0x34, (byte) 0x4d, (byte) 0x14, (byte) 0xa1, + (byte) 0x69, (byte) 0x9a, (byte) 0x26, (byte) 0x8a, + (byte) 0xf0, (byte) 0x3c, (byte) 0xcf, (byte) 0x13, + (byte) 0x45, (byte) 0x78, (byte) 0x9e, (byte) 0xe7, + (byte) 0x99, (byte) 0x26, (byte) 0x44, (byte) 0xd1, + (byte) 0xf3, (byte) 0x4c, (byte) 0x13, (byte) 0xa2, + (byte) 0xe8, (byte) 0x79, (byte) 0xa6, (byte) 0x09, + (byte) 0xd3, (byte) 0x14, (byte) 0x45, (byte) 0xd3, + (byte) 0x04, (byte) 0xa2, (byte) 0x68, (byte) 0x9a, + (byte) 0x02, (byte) 0x00, (byte) 0x00, (byte) 0x0a, + (byte) 0x1c, (byte) 0x00, (byte) 0x00, (byte) 0x02, + (byte) 0x6c, (byte) 0xd0, (byte) 0x94, (byte) 0x58, + (byte) 0x1c, (byte) 0xa0, (byte) 0xd0, (byte) 0x90, + (byte) 0x95, (byte) 0x00, (byte) 0x40, (byte) 0x48, + (byte) 0x00, (byte) 0x80, (byte) 0x41, (byte) 0x51, + (byte) 0x2c, (byte) 0xcb, (byte) 0xf3, (byte) 0x44, + (byte) 0x51, (byte) 0x14, (byte) 0x4d, (byte) 0x53, + (byte) 0x55, (byte) 0x5d, (byte) 0x17, (byte) 0x9a, + (byte) 0xe6, (byte) 0x79, (byte) 0xa2, (byte) 0x28, + (byte) 0x8a, (byte) 0xa6, (byte) 0xa9, (byte) 0xaa, + (byte) 0xae, (byte) 0x0b, (byte) 0x4d, (byte) 0xf3, + (byte) 0x3c, (byte) 0x51, (byte) 0x14, (byte) 0x45, + (byte) 0xd3, (byte) 0x54, (byte) 0x55, (byte) 0xd7, + (byte) 0x85, (byte) 0xe7, (byte) 0x79, (byte) 0xa2, + (byte) 0x29, (byte) 0x9a, (byte) 0xa6, (byte) 0x69, + (byte) 0xaa, (byte) 0xaa, (byte) 0xeb, (byte) 0xc2, + (byte) 0xf3, (byte) 0x44, (byte) 0xd1, (byte) 0x34, + (byte) 0x4d, (byte) 0x53, (byte) 0x55, (byte) 0x55, + (byte) 0xd7, (byte) 0x75, (byte) 0xe1, (byte) 0x79, + (byte) 0xa2, (byte) 0x68, (byte) 0x9a, (byte) 0xa6, + (byte) 0xa9, (byte) 0xaa, (byte) 0xae, (byte) 0xeb, + (byte) 0xba, (byte) 0xf0, (byte) 0x3c, (byte) 0x51, + (byte) 0x34, (byte) 0x4d, (byte) 0xd3, (byte) 0x54, + (byte) 0x55, (byte) 0xd7, (byte) 0x95, (byte) 0x65, + (byte) 0x88, (byte) 0xa2, (byte) 0x28, (byte) 0x9a, + (byte) 0xa6, (byte) 0x69, (byte) 0xaa, (byte) 0xaa, + (byte) 0xeb, (byte) 0xca, (byte) 0x32, (byte) 0x10, + (byte) 0x45, (byte) 0xd3, (byte) 0x34, (byte) 0x4d, + (byte) 0x55, (byte) 0x75, (byte) 0x5d, (byte) 0x59, + (byte) 0x06, (byte) 0xa2, (byte) 0x68, (byte) 0x9a, + (byte) 0xaa, (byte) 0xea, (byte) 0xba, (byte) 0xae, + (byte) 0x2b, (byte) 0xcb, (byte) 0x40, (byte) 0x14, + (byte) 0x4d, (byte) 0x53, (byte) 0x55, (byte) 0x5d, + (byte) 0xd7, (byte) 0x75, (byte) 0x65, (byte) 0x19, + (byte) 0x98, (byte) 0xa6, (byte) 0x6a, (byte) 0xaa, + (byte) 0xaa, (byte) 0xeb, (byte) 0xca, (byte) 0xb2, + (byte) 0x2c, (byte) 0x03, (byte) 0x4c, (byte) 0x53, + (byte) 0x55, (byte) 0x5d, (byte) 0x57, (byte) 0x96, + (byte) 0x65, (byte) 0x19, (byte) 0xa0, (byte) 0xaa, + (byte) 0xae, (byte) 0xeb, (byte) 0xba, (byte) 0xb2, + (byte) 0x6c, (byte) 0xdb, (byte) 0x00, (byte) 0x55, + (byte) 0x75, (byte) 0x5d, (byte) 0xd7, (byte) 0x95, + (byte) 0x65, (byte) 0xdb, (byte) 0x06, (byte) 0xb8, + (byte) 0xae, (byte) 0xeb, (byte) 0xca, (byte) 0xb2, + (byte) 0x2c, (byte) 0xdb, (byte) 0x36, (byte) 0x00, + (byte) 0xd7, (byte) 0x95, (byte) 0x65, (byte) 0x59, + (byte) 0xb6, (byte) 0x6d, (byte) 0x01, (byte) 0x00, + (byte) 0x00, (byte) 0x07, (byte) 0x0e, (byte) 0x00, + (byte) 0x00, (byte) 0x01, (byte) 0x46, (byte) 0xd0, + (byte) 0x49, (byte) 0x46, (byte) 0x95, (byte) 0x45, + (byte) 0xd8, (byte) 0x68, (byte) 0xc2, (byte) 0x85, + (byte) 0x07, (byte) 0xa0, (byte) 0xd0, (byte) 0x90, + (byte) 0x15, (byte) 0x01, (byte) 0x40, (byte) 0x14, + (byte) 0x00, (byte) 0x00, (byte) 0x60, (byte) 0x8c, + (byte) 0x52, (byte) 0x8a, (byte) 0x29, (byte) 0x65, + (byte) 0x18, (byte) 0x93, (byte) 0x50, (byte) 0x4a, + (byte) 0x09, (byte) 0x0d, (byte) 0x63, (byte) 0x52, + (byte) 0x4a, (byte) 0x2a, (byte) 0xa5, (byte) 0x92, + (byte) 0x92, (byte) 0x52, (byte) 0x4a, (byte) 0xa5, + (byte) 0x54, (byte) 0x12, (byte) 0x52, (byte) 0x4a, + (byte) 0xa9, (byte) 0x94, (byte) 0x4a, (byte) 0x4a, + (byte) 0x4a, (byte) 0x29, (byte) 0x95, (byte) 0x92, + (byte) 0x51, (byte) 0x4a, (byte) 0x29, (byte) 0xb5, + (byte) 0x96, (byte) 0x2a, (byte) 0x29, (byte) 0xa9, + (byte) 0x94, (byte) 0x94, (byte) 0x52, (byte) 0x25, + (byte) 0xa5, (byte) 0xa4, (byte) 0x92, (byte) 0x52, + (byte) 0x2a, (byte) 0x00, (byte) 0x00, (byte) 0xec, + (byte) 0xc0, (byte) 0x01, (byte) 0x00, (byte) 0xec, + (byte) 0xc0, (byte) 0x42, (byte) 0x28, (byte) 0x34, + (byte) 0x64, (byte) 0x25, (byte) 0x00, (byte) 0x90, + (byte) 0x07, (byte) 0x00, (byte) 0x40, (byte) 0x10, + (byte) 0x82, (byte) 0x14, (byte) 0x63, (byte) 0x8c, + (byte) 0x39, (byte) 0x27, (byte) 0xa5, (byte) 0x54, + (byte) 0x8a, (byte) 0x31, (byte) 0xe7, (byte) 0x9c, + (byte) 0x93, (byte) 0x52, (byte) 0x2a, (byte) 0xc5, + (byte) 0x98, (byte) 0x73, (byte) 0xce, (byte) 0x49, + (byte) 0x29, (byte) 0x19, (byte) 0x63, (byte) 0xcc, + (byte) 0x39, (byte) 0xe7, (byte) 0xa4, (byte) 0x94, + (byte) 0x8c, (byte) 0x31, (byte) 0xe6, (byte) 0x9c, + (byte) 0x73, (byte) 0x52, (byte) 0x4a, (byte) 0xc6, + (byte) 0x9c, (byte) 0x73, (byte) 0xce, (byte) 0x39, + (byte) 0x29, (byte) 0x25, (byte) 0x63, (byte) 0xce, + (byte) 0x39, (byte) 0xe7, (byte) 0x9c, (byte) 0x94, + (byte) 0xd2, (byte) 0x39, (byte) 0xe7, (byte) 0x9c, + (byte) 0x83, (byte) 0x50, (byte) 0x4a, (byte) 0x29, + (byte) 0xa5, (byte) 0x73, (byte) 0xce, (byte) 0x41, + (byte) 0x28, (byte) 0xa5, (byte) 0x94, (byte) 0x12, + (byte) 0x42, (byte) 0xe7, (byte) 0x20, (byte) 0x94, + (byte) 0x52, (byte) 0x4a, (byte) 0xe9, (byte) 0x9c, + (byte) 0x73, (byte) 0x10, (byte) 0x0a, (byte) 0x00, + (byte) 0x00, (byte) 0x2a, (byte) 0x70, (byte) 0x00, + (byte) 0x00, (byte) 0x08, (byte) 0xb0, (byte) 0x51, + (byte) 0x64, (byte) 0x73, (byte) 0x82, (byte) 0x91, + (byte) 0xa0, (byte) 0x42, (byte) 0x43, (byte) 0x56, + (byte) 0x02, (byte) 0x00, (byte) 0xa9, (byte) 0x00, + (byte) 0x00, (byte) 0x06, (byte) 0xc7, (byte) 0xb1, + (byte) 0x2c, (byte) 0x4d, (byte) 0xd3, (byte) 0x34, + (byte) 0xcf, (byte) 0x13, (byte) 0x45, (byte) 0x4b, + (byte) 0x92, (byte) 0x34, (byte) 0xcf, (byte) 0x13, + (byte) 0x3d, (byte) 0x4f, (byte) 0x14, (byte) 0x4d, + (byte) 0xd5, (byte) 0x92, (byte) 0x24, (byte) 0xcf, + (byte) 0x13, (byte) 0x45, (byte) 0xcf, (byte) 0x13, + (byte) 0x4d, (byte) 0x53, (byte) 0xe5, (byte) 0x79, + (byte) 0x9e, (byte) 0x28, (byte) 0x8a, (byte) 0xa2, + (byte) 0x68, (byte) 0x9a, (byte) 0xaa, (byte) 0x4a, + (byte) 0x14, (byte) 0x45, (byte) 0x4f, (byte) 0x14, + (byte) 0x45, (byte) 0xd1, (byte) 0x34, (byte) 0x55, + (byte) 0x95, (byte) 0x2c, (byte) 0x8b, (byte) 0xa2, + (byte) 0x69, (byte) 0x9a, (byte) 0xa6, (byte) 0xaa, + (byte) 0xba, (byte) 0x2e, (byte) 0x5b, (byte) 0x16, + (byte) 0x45, (byte) 0xd3, (byte) 0x34, (byte) 0x4d, + (byte) 0x55, (byte) 0x75, (byte) 0x5d, (byte) 0x98, + (byte) 0xa6, (byte) 0x28, (byte) 0xaa, (byte) 0xaa, + (byte) 0xeb, (byte) 0xca, (byte) 0x2e, (byte) 0x4c, + (byte) 0x53, (byte) 0x14, (byte) 0x4d, (byte) 0xd3, + (byte) 0x75, (byte) 0x65, (byte) 0x19, (byte) 0xb2, + (byte) 0xad, (byte) 0x9a, (byte) 0xaa, (byte) 0xea, + (byte) 0xba, (byte) 0xb2, (byte) 0x0d, (byte) 0xdb, + (byte) 0x36, (byte) 0x4d, (byte) 0x55, (byte) 0x75, + (byte) 0x5d, (byte) 0x59, (byte) 0x06, (byte) 0xae, + (byte) 0xeb, (byte) 0xba, (byte) 0xb2, (byte) 0x6c, + (byte) 0xeb, (byte) 0xc0, (byte) 0x75, (byte) 0x5d, + (byte) 0x57, (byte) 0x96, (byte) 0x6d, (byte) 0x5d, + (byte) 0x00, (byte) 0x00, (byte) 0x78, (byte) 0x82, + (byte) 0x03, (byte) 0x00, (byte) 0x50, (byte) 0x81, + (byte) 0x0d, (byte) 0xab, (byte) 0x23, (byte) 0x9c, + (byte) 0x14, (byte) 0x8d, (byte) 0x05, (byte) 0x16, + (byte) 0x1a, (byte) 0xb2, (byte) 0x12, (byte) 0x00, + (byte) 0xc8, (byte) 0x00, (byte) 0x00, (byte) 0x20, + (byte) 0x08, (byte) 0x41, (byte) 0x48, (byte) 0x29, + (byte) 0x85, (byte) 0x90, (byte) 0x52, (byte) 0x0a, + (byte) 0x21, (byte) 0xa5, (byte) 0x14, (byte) 0x42, + (byte) 0x4a, (byte) 0x29, (byte) 0x84, (byte) 0x04, + (byte) 0x00, (byte) 0x00, (byte) 0x0c, (byte) 0x38, + (byte) 0x00, (byte) 0x00, (byte) 0x04, (byte) 0x98, + (byte) 0x50, (byte) 0x06, (byte) 0x0a, (byte) 0x0d, + (byte) 0x59, (byte) 0x09, (byte) 0x00, (byte) 0xa4, + (byte) 0x02, (byte) 0x00, (byte) 0x00, (byte) 0x10, + (byte) 0x42, (byte) 0x08, (byte) 0x21, (byte) 0x84, + (byte) 0x10, (byte) 0x42, (byte) 0x08, (byte) 0x21, + (byte) 0x84, (byte) 0x10, (byte) 0x42, (byte) 0x08, + (byte) 0x21, (byte) 0x84, (byte) 0x10, (byte) 0x42, + (byte) 0x08, (byte) 0x21, (byte) 0x84, (byte) 0x10, + (byte) 0x42, (byte) 0x08, (byte) 0x21, (byte) 0x84, + (byte) 0x10, (byte) 0x42, (byte) 0x08, (byte) 0x21, + (byte) 0x84, (byte) 0x10, (byte) 0x42, (byte) 0x08, + (byte) 0x21, (byte) 0x84, (byte) 0x10, (byte) 0x42, + (byte) 0x08, (byte) 0x21, (byte) 0x84, (byte) 0x10, + (byte) 0x42, (byte) 0x08, (byte) 0x21, (byte) 0x84, + (byte) 0x10, (byte) 0x42, (byte) 0x08, (byte) 0x21, + (byte) 0x84, (byte) 0xce, (byte) 0x39, (byte) 0xe7, + (byte) 0x9c, (byte) 0x73, (byte) 0xce, (byte) 0x39, + (byte) 0xe7, (byte) 0x9c, (byte) 0x73, (byte) 0xce, + (byte) 0x39, (byte) 0xe7, (byte) 0x9c, (byte) 0x73, + (byte) 0xce, (byte) 0x39, (byte) 0xe7, (byte) 0x9c, + (byte) 0x73, (byte) 0xce, (byte) 0x39, (byte) 0xe7, + (byte) 0x9c, (byte) 0x73, (byte) 0xce, (byte) 0x39, + (byte) 0xe7, (byte) 0x9c, (byte) 0x73, (byte) 0xce, + (byte) 0x39, (byte) 0xe7, (byte) 0x9c, (byte) 0x73, + (byte) 0xce, (byte) 0x39, (byte) 0xe7, (byte) 0x9c, + (byte) 0x73, (byte) 0xce, (byte) 0x39, (byte) 0xe7, + (byte) 0x9c, (byte) 0x73, (byte) 0xce, (byte) 0x39, + (byte) 0xe7, (byte) 0x9c, (byte) 0x73, (byte) 0xce, + (byte) 0x39, (byte) 0xe7, (byte) 0x9c, (byte) 0x73, + (byte) 0x02, (byte) 0x00, (byte) 0xb1, (byte) 0x2b, + (byte) 0x1c, (byte) 0x00, (byte) 0x76, (byte) 0x22, + (byte) 0x6c, (byte) 0x58, (byte) 0x1d, (byte) 0xe1, + (byte) 0xa4, (byte) 0x68, (byte) 0x2c, (byte) 0xb0, + (byte) 0xd0, (byte) 0x90, (byte) 0x95, (byte) 0x00, + (byte) 0x40, (byte) 0x38, (byte) 0x00, (byte) 0x00, + (byte) 0x60, (byte) 0x8c, (byte) 0x31, (byte) 0xce, + (byte) 0x59, (byte) 0xac, (byte) 0xb5, (byte) 0xd6, + (byte) 0x5a, (byte) 0x2b, (byte) 0xa5, (byte) 0x94, + (byte) 0x92, (byte) 0x50, (byte) 0x6b, (byte) 0xad, + (byte) 0xb5, (byte) 0xd6, (byte) 0x9a, (byte) 0x29, + (byte) 0xa4, (byte) 0x94, (byte) 0x84, (byte) 0x16, + (byte) 0x63, (byte) 0x8c, (byte) 0x31, (byte) 0xc6, + (byte) 0x98, (byte) 0x31, (byte) 0x08, (byte) 0x29, + (byte) 0xb5, (byte) 0x18, (byte) 0x63, (byte) 0x8c, + (byte) 0x31, (byte) 0xc6, (byte) 0x8c, (byte) 0x39, + (byte) 0x47, (byte) 0x2d, (byte) 0xc6, (byte) 0x18, + (byte) 0x63, (byte) 0x8c, (byte) 0x31, (byte) 0xb6, + (byte) 0x56, (byte) 0x4a, (byte) 0x6c, (byte) 0x31, + (byte) 0xc6, (byte) 0x18, (byte) 0x63, (byte) 0x8c, + (byte) 0xb1, (byte) 0xb5, (byte) 0x52, (byte) 0x62, + (byte) 0x8c, (byte) 0x31, (byte) 0xc6, (byte) 0x18, + (byte) 0x63, (byte) 0x8c, (byte) 0x31, (byte) 0xc6, + (byte) 0x16, (byte) 0x5b, (byte) 0x8c, (byte) 0x31, + (byte) 0xc6, (byte) 0x18, (byte) 0x63, (byte) 0x8c, + (byte) 0x31, (byte) 0xb6, (byte) 0x18, (byte) 0x63, + (byte) 0x8c, (byte) 0x31, (byte) 0xc6, (byte) 0x18, + (byte) 0x63, (byte) 0x8c, (byte) 0x31, (byte) 0xc6, + (byte) 0x18, (byte) 0x63, (byte) 0x8c, (byte) 0x31, + (byte) 0xc6, (byte) 0x18, (byte) 0x63, (byte) 0x8c, + (byte) 0x31, (byte) 0xb6, (byte) 0x18, (byte) 0x63, + (byte) 0x8c, (byte) 0x31, (byte) 0xc6, (byte) 0x18, + (byte) 0x63, (byte) 0x8c, (byte) 0x31, (byte) 0xc6, + (byte) 0x18, (byte) 0x63, (byte) 0x8c, (byte) 0x31, + (byte) 0xc6, (byte) 0x18, (byte) 0x63, (byte) 0x8c, + (byte) 0x31, (byte) 0xc6, (byte) 0x18, (byte) 0x63, + (byte) 0x8c, (byte) 0x31, (byte) 0xc6, (byte) 0x18, + (byte) 0x63, (byte) 0x6c, (byte) 0x31, (byte) 0xc6, + (byte) 0x18, (byte) 0x63, (byte) 0x8c, (byte) 0x31, + (byte) 0xc6, (byte) 0x18, (byte) 0x63, (byte) 0x8c, + (byte) 0x31, (byte) 0xc6, (byte) 0x18, (byte) 0x63, + (byte) 0x2c, (byte) 0x00, (byte) 0xc0, (byte) 0xe4, + (byte) 0xc1, (byte) 0x01, (byte) 0x00, (byte) 0x2a, + (byte) 0xc1, (byte) 0xc6, (byte) 0x19, (byte) 0x56, + (byte) 0x92, (byte) 0xce, (byte) 0x0a, (byte) 0x47, + (byte) 0x83, (byte) 0x0b, (byte) 0x0d, (byte) 0x59, + (byte) 0x09, (byte) 0x00, (byte) 0xe4, (byte) 0x06, + (byte) 0x00, (byte) 0x00, (byte) 0xc6, (byte) 0x28, + (byte) 0xc5, (byte) 0x98, (byte) 0x63, (byte) 0xce, + (byte) 0x41, (byte) 0x08, (byte) 0xa1, (byte) 0x94, + (byte) 0x12, (byte) 0x4a, (byte) 0x49, (byte) 0xad, + (byte) 0x75, (byte) 0xce, (byte) 0x39, (byte) 0x08, + (byte) 0x21, (byte) 0x94, (byte) 0x52, (byte) 0x4a, + (byte) 0x49, (byte) 0xa9, (byte) 0xb4, (byte) 0x94, + (byte) 0x62, (byte) 0xca, (byte) 0x98, (byte) 0x73, + (byte) 0xce, (byte) 0x41, (byte) 0x08, (byte) 0xa5, + (byte) 0x94, (byte) 0x12, (byte) 0x4a, (byte) 0x49, + (byte) 0xa9, (byte) 0xa5, (byte) 0xd4, (byte) 0x39, + (byte) 0xe7, (byte) 0x20, (byte) 0x94, (byte) 0x52, + (byte) 0x4a, (byte) 0x4a, (byte) 0x29, (byte) 0xa5, + (byte) 0x94, (byte) 0x5a, (byte) 0x6a, (byte) 0xad, + (byte) 0x73, (byte) 0x10, (byte) 0x42, (byte) 0x08, + (byte) 0xa5, (byte) 0x94, (byte) 0x52, (byte) 0x4a, + (byte) 0x4a, (byte) 0x29, (byte) 0xa5, (byte) 0xd4, + (byte) 0x52, (byte) 0x08, (byte) 0x21, (byte) 0x94, + (byte) 0x52, (byte) 0x4a, (byte) 0x2a, (byte) 0x29, + (byte) 0xa5, (byte) 0x94, (byte) 0x52, (byte) 0x6b, + (byte) 0xad, (byte) 0xa5, (byte) 0x10, (byte) 0x42, + (byte) 0x28, (byte) 0xa5, (byte) 0x94, (byte) 0x94, + (byte) 0x52, (byte) 0x4a, (byte) 0x29, (byte) 0xa5, + (byte) 0xd4, (byte) 0x5a, (byte) 0x8b, (byte) 0xa1, + (byte) 0x94, (byte) 0x90, (byte) 0x4a, (byte) 0x29, + (byte) 0x25, (byte) 0xa5, (byte) 0x94, (byte) 0x52, + (byte) 0x49, (byte) 0x2d, (byte) 0xb5, (byte) 0x96, + (byte) 0x5a, (byte) 0x2a, (byte) 0xa1, (byte) 0x94, + (byte) 0x54, (byte) 0x52, (byte) 0x4a, (byte) 0x29, + (byte) 0xa5, (byte) 0x94, (byte) 0x52, (byte) 0x6b, + (byte) 0xa9, (byte) 0xb5, (byte) 0x56, (byte) 0x4a, + (byte) 0x49, (byte) 0x25, (byte) 0xa5, (byte) 0x94, + (byte) 0x52, (byte) 0x4a, (byte) 0x29, (byte) 0xa5, + (byte) 0xd4, (byte) 0x62, (byte) 0x6b, (byte) 0x29, + (byte) 0x94, (byte) 0x92, (byte) 0x52, (byte) 0x49, + (byte) 0x29, (byte) 0xb5, (byte) 0x94, (byte) 0x52, + (byte) 0x4a, (byte) 0xad, (byte) 0xc5, (byte) 0xd8, + (byte) 0x62, (byte) 0x29, (byte) 0xad, (byte) 0xa4, + (byte) 0x94, (byte) 0x52, (byte) 0x4a, (byte) 0x29, + (byte) 0xa5, (byte) 0xd6, (byte) 0x52, (byte) 0x6c, + (byte) 0xad, (byte) 0xb5, (byte) 0xd8, (byte) 0x52, + (byte) 0x4a, (byte) 0x29, (byte) 0xa5, (byte) 0x96, + (byte) 0x5a, (byte) 0x4a, (byte) 0x29, (byte) 0xb5, + (byte) 0x16, (byte) 0x5b, (byte) 0x6a, (byte) 0x2d, + (byte) 0xa5, (byte) 0x94, (byte) 0x52, (byte) 0x4b, + (byte) 0x29, (byte) 0xa5, (byte) 0x96, (byte) 0x52, + (byte) 0x4b, (byte) 0x2d, (byte) 0xc6, (byte) 0xd6, + (byte) 0x5a, (byte) 0x4b, (byte) 0x29, (byte) 0xa5, + (byte) 0xd4, (byte) 0x52, (byte) 0x6a, (byte) 0xa9, + (byte) 0xa5, (byte) 0x94, (byte) 0x52, (byte) 0x6c, + (byte) 0xad, (byte) 0xb5, (byte) 0x98, (byte) 0x52, + (byte) 0x6a, (byte) 0x2d, (byte) 0xa5, (byte) 0xd4, + (byte) 0x52, (byte) 0x6b, (byte) 0x2d, (byte) 0xb5, + (byte) 0xd8, (byte) 0x52, (byte) 0x6a, (byte) 0x2d, + (byte) 0xb5, (byte) 0x94, (byte) 0x52, (byte) 0x6b, + (byte) 0xa9, (byte) 0xa5, (byte) 0x94, (byte) 0x5a, + (byte) 0x6b, (byte) 0x2d, (byte) 0xb6, (byte) 0xd8, + (byte) 0x5a, (byte) 0x6b, (byte) 0x29, (byte) 0xb5, + (byte) 0x94, (byte) 0x52, (byte) 0x4a, (byte) 0xa9, + (byte) 0xb5, (byte) 0x16, (byte) 0x5b, (byte) 0x8a, + (byte) 0xb1, (byte) 0xb5, (byte) 0xd4, (byte) 0x4a, + (byte) 0x4a, (byte) 0x29, (byte) 0xb5, (byte) 0xd4, + (byte) 0x5a, (byte) 0x6a, (byte) 0x2d, (byte) 0xb6, + (byte) 0x16, (byte) 0x5b, (byte) 0x6b, (byte) 0xad, + (byte) 0xa5, (byte) 0xd6, (byte) 0x5a, (byte) 0x6a, + (byte) 0x29, (byte) 0xa5, (byte) 0x16, (byte) 0x5b, + (byte) 0x8c, (byte) 0x31, (byte) 0xc6, (byte) 0x16, + (byte) 0x63, (byte) 0x6b, (byte) 0x31, (byte) 0xa5, + (byte) 0x94, (byte) 0x52, (byte) 0x4b, (byte) 0xa9, + (byte) 0xa5, (byte) 0x02, (byte) 0x00, (byte) 0x80, + (byte) 0x0e, (byte) 0x1c, (byte) 0x00, (byte) 0x00, + (byte) 0x02, (byte) 0x8c, (byte) 0xa8, (byte) 0xb4, + (byte) 0x10, (byte) 0x3b, (byte) 0xcd, (byte) 0xb8, + (byte) 0xf2, (byte) 0x08, (byte) 0x1c, (byte) 0x51, + (byte) 0xc8, (byte) 0x30, (byte) 0x01, (byte) 0x15, + (byte) 0x1a, (byte) 0xb2, (byte) 0x12, (byte) 0x00, + (byte) 0x20, (byte) 0x03, (byte) 0x00, (byte) 0x20, + (byte) 0x90, (byte) 0x69, (byte) 0x92, (byte) 0x39, + (byte) 0x49, (byte) 0xa9, (byte) 0x11, (byte) 0x26, + (byte) 0x39, (byte) 0xc5, (byte) 0xa0, (byte) 0x94, + (byte) 0xe6, (byte) 0x9c, (byte) 0x53, (byte) 0x4a, + (byte) 0x29, (byte) 0xa5, (byte) 0x34, (byte) 0x44, + (byte) 0x96, (byte) 0x64, (byte) 0x90, (byte) 0x62, + (byte) 0x50, (byte) 0x1d, (byte) 0x99, (byte) 0x8c, + (byte) 0x39, (byte) 0x49, (byte) 0x39, (byte) 0x43, + (byte) 0xa4, (byte) 0x31, (byte) 0xa4, (byte) 0x20, + (byte) 0xf5, (byte) 0x4c, (byte) 0x91, (byte) 0xc7, + (byte) 0x94, (byte) 0x62, (byte) 0x10, (byte) 0x43, + (byte) 0x48, (byte) 0x2a, (byte) 0x74, (byte) 0x8a, + (byte) 0x39, (byte) 0x6c, (byte) 0x35, (byte) 0xf9, + (byte) 0x58, (byte) 0x42, (byte) 0x07, (byte) 0xb1, + (byte) 0x06, (byte) 0x65, (byte) 0x8c, (byte) 0x70, + (byte) 0x29, (byte) 0xc5, (byte) 0x00, (byte) 0x00, + (byte) 0x00, (byte) 0x08, (byte) 0x02, (byte) 0x00, + (byte) 0x04, (byte) 0x84, (byte) 0x04, (byte) 0x00, + (byte) 0x18, (byte) 0x20, (byte) 0x28, (byte) 0x98, + (byte) 0x01, (byte) 0x00, (byte) 0x06, (byte) 0x07, + (byte) 0x08, (byte) 0x23, (byte) 0x07, (byte) 0x02, + (byte) 0x1d, (byte) 0x01, (byte) 0x04, (byte) 0x0e, + (byte) 0x6d, (byte) 0x00, (byte) 0x80, (byte) 0x81, + (byte) 0x08, (byte) 0x99, (byte) 0x09, (byte) 0x0c, + (byte) 0x0a, (byte) 0xa1, (byte) 0xc1, (byte) 0x41, + (byte) 0x26, (byte) 0x00, (byte) 0x3c, (byte) 0x40, + (byte) 0x44, (byte) 0x48, (byte) 0x05, (byte) 0x00, + (byte) 0x89, (byte) 0x09, (byte) 0x8a, (byte) 0xd2, + (byte) 0x85, (byte) 0x2e, (byte) 0x08, (byte) 0x21, + (byte) 0x82, (byte) 0x74, (byte) 0x11, (byte) 0x64, + (byte) 0xf1, (byte) 0xc0, (byte) 0x85, (byte) 0x13, + (byte) 0x37, (byte) 0x9e, (byte) 0xb8, (byte) 0xe1, + (byte) 0x84, (byte) 0x0e, (byte) 0x6d, (byte) 0x20, + (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, + (byte) 0x00, (byte) 0x20, (byte) 0x00, (byte) 0xf0, + (byte) 0x01, (byte) 0x00, (byte) 0x90, (byte) 0x50, + (byte) 0x00, (byte) 0x11, (byte) 0x11, (byte) 0xd1, + (byte) 0xcc, (byte) 0x55, (byte) 0x58, (byte) 0x5c, + (byte) 0x60, (byte) 0x64, (byte) 0x68, (byte) 0x6c, + (byte) 0x70, (byte) 0x74, (byte) 0x78, (byte) 0x7c, + (byte) 0x80, (byte) 0x84, (byte) 0x08, (byte) 0x00, + (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, + (byte) 0x10, (byte) 0x00, (byte) 0x7c, (byte) 0x00, + (byte) 0x00, (byte) 0x24, (byte) 0x22, (byte) 0x40, + (byte) 0x44, (byte) 0x44, (byte) 0x34, (byte) 0x73, + (byte) 0x15, (byte) 0x16, (byte) 0x17, (byte) 0x18, + (byte) 0x19, (byte) 0x1a, (byte) 0x1b, (byte) 0x1c, + (byte) 0x1d, (byte) 0x1e, (byte) 0x1f, (byte) 0x20, + (byte) 0x21, (byte) 0x01, (byte) 0x00, (byte) 0x80, + (byte) 0x00, (byte) 0x02, (byte) 0x00, (byte) 0x00, + (byte) 0x00, (byte) 0x00, (byte) 0x20, (byte) 0x80, + (byte) 0x00, (byte) 0x04, (byte) 0x04, (byte) 0x04, + (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, + (byte) 0x00, (byte) 0x02, (byte) 0x00, (byte) 0x00, + (byte) 0x00, (byte) 0x04, (byte) 0x04 + }; +} diff --git a/library/core/src/test/java/com/google/android/exoplayer2/RobolectricUtil.java b/testutils_robolectric/src/main/java/com/google/android/exoplayer2/testutil/RobolectricUtil.java similarity index 97% rename from library/core/src/test/java/com/google/android/exoplayer2/RobolectricUtil.java rename to testutils_robolectric/src/main/java/com/google/android/exoplayer2/testutil/RobolectricUtil.java index 0b5740b72c..e606fd104b 100644 --- a/library/core/src/test/java/com/google/android/exoplayer2/RobolectricUtil.java +++ b/testutils_robolectric/src/main/java/com/google/android/exoplayer2/testutil/RobolectricUtil.java @@ -13,7 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package com.google.android.exoplayer2; +package com.google.android.exoplayer2.testutil; import static org.robolectric.Shadows.shadowOf; import static org.robolectric.util.ReflectionHelpers.callInstanceMethod; @@ -185,9 +185,9 @@ public final class RobolectricUtil { @Override public int compareTo(@NonNull PendingMessage other) { - int res = Long.compare(this.when, other.when); + int res = Util.compareLong(this.when, other.when); if (res == 0 && this != other) { - res = Long.compare(this.sequenceNumber, other.sequenceNumber); + res = Util.compareLong(this.sequenceNumber, other.sequenceNumber); } return res; } diff --git a/testutils/src/main/java/com/google/android/exoplayer2/testutil/StubExoPlayer.java b/testutils_robolectric/src/main/java/com/google/android/exoplayer2/testutil/StubExoPlayer.java similarity index 99% rename from testutils/src/main/java/com/google/android/exoplayer2/testutil/StubExoPlayer.java rename to testutils_robolectric/src/main/java/com/google/android/exoplayer2/testutil/StubExoPlayer.java index 40d5b6c3f9..af8b10e6d3 100644 --- a/testutils/src/main/java/com/google/android/exoplayer2/testutil/StubExoPlayer.java +++ b/testutils_robolectric/src/main/java/com/google/android/exoplayer2/testutil/StubExoPlayer.java @@ -271,5 +271,4 @@ public abstract class StubExoPlayer implements ExoPlayer { public long getContentPosition() { throw new UnsupportedOperationException(); } - } diff --git a/testutils/src/main/java/com/google/android/exoplayer2/testutil/TimelineAsserts.java b/testutils_robolectric/src/main/java/com/google/android/exoplayer2/testutil/TimelineAsserts.java similarity index 84% rename from testutils/src/main/java/com/google/android/exoplayer2/testutil/TimelineAsserts.java rename to testutils_robolectric/src/main/java/com/google/android/exoplayer2/testutil/TimelineAsserts.java index 42136bfe4d..abef8e06be 100644 --- a/testutils/src/main/java/com/google/android/exoplayer2/testutil/TimelineAsserts.java +++ b/testutils_robolectric/src/main/java/com/google/android/exoplayer2/testutil/TimelineAsserts.java @@ -23,9 +23,7 @@ import com.google.android.exoplayer2.Timeline; import com.google.android.exoplayer2.Timeline.Period; import com.google.android.exoplayer2.Timeline.Window; -/** - * Unit test for {@link Timeline}. - */ +/** Unit test for {@link Timeline}. */ public final class TimelineAsserts { private static final int[] REPEAT_MODES = { @@ -34,9 +32,7 @@ public final class TimelineAsserts { private TimelineAsserts() {} - /** - * Assert that timeline is empty (i.e. has no windows or periods). - */ + /** Assert that timeline is empty (i.e. has no windows or periods). */ public static void assertEmpty(Timeline timeline) { assertWindowIds(timeline); assertPeriodCounts(timeline); @@ -63,9 +59,7 @@ public final class TimelineAsserts { } } - /** - * Asserts that window properties {@link Window}.isDynamic are set correctly. - */ + /** Asserts that window properties {@link Window}.isDynamic are set correctly. */ public static void assertWindowIsDynamic(Timeline timeline, boolean... windowIsDynamic) { Window window = new Window(); for (int i = 0; i < timeline.getWindowCount(); i++) { @@ -78,8 +72,10 @@ public final class TimelineAsserts { * Asserts that previous window indices for each window depending on the repeat mode and the * shuffle mode are equal to the given sequence. */ - public static void assertPreviousWindowIndices(Timeline timeline, - @Player.RepeatMode int repeatMode, boolean shuffleModeEnabled, + public static void assertPreviousWindowIndices( + Timeline timeline, + @Player.RepeatMode int repeatMode, + boolean shuffleModeEnabled, int... expectedPreviousWindowIndices) { for (int i = 0; i < timeline.getWindowCount(); i++) { assertThat(timeline.getPreviousWindowIndex(i, repeatMode, shuffleModeEnabled)) @@ -88,11 +84,14 @@ public final class TimelineAsserts { } /** - * Asserts that next window indices for each window depending on the repeat mode and the - * shuffle mode are equal to the given sequence. + * Asserts that next window indices for each window depending on the repeat mode and the shuffle + * mode are equal to the given sequence. */ - public static void assertNextWindowIndices(Timeline timeline, @Player.RepeatMode int repeatMode, - boolean shuffleModeEnabled, int... expectedNextWindowIndices) { + public static void assertNextWindowIndices( + Timeline timeline, + @Player.RepeatMode int repeatMode, + boolean shuffleModeEnabled, + int... expectedNextWindowIndices) { for (int i = 0; i < timeline.getWindowCount(); i++) { assertThat(timeline.getNextWindowIndex(i, repeatMode, shuffleModeEnabled)) .isEqualTo(expectedNextWindowIndices[i]); @@ -113,9 +112,9 @@ public final class TimelineAsserts { } /** - * Asserts that period counts for each window are set correctly. Also asserts that - * {@link Window#firstPeriodIndex} and {@link Window#lastPeriodIndex} are set correctly, and it - * asserts the correct behavior of {@link Timeline#getNextWindowIndex(int, int, boolean)}. + * Asserts that period counts for each window are set correctly. Also asserts that {@link + * Window#firstPeriodIndex} and {@link Window#lastPeriodIndex} are set correctly, and it asserts + * the correct behavior of {@link Timeline#getNextWindowIndex(int, int, boolean)}. */ public static void assertPeriodCounts(Timeline timeline, int... expectedPeriodCounts) { int windowCount = timeline.getWindowCount(); @@ -147,8 +146,8 @@ public final class TimelineAsserts { .isEqualTo(i + 1); } else { int nextWindow = timeline.getNextWindowIndex(expectedWindowIndex, repeatMode, false); - int nextPeriod = nextWindow == C.INDEX_UNSET ? C.INDEX_UNSET - : accumulatedPeriodCounts[nextWindow]; + int nextPeriod = + nextWindow == C.INDEX_UNSET ? C.INDEX_UNSET : accumulatedPeriodCounts[nextWindow]; assertThat(timeline.getNextPeriodIndex(i, period, window, repeatMode, false)) .isEqualTo(nextPeriod); } @@ -156,9 +155,7 @@ public final class TimelineAsserts { } } - /** - * Asserts that periods' {@link Period#getAdGroupCount()} are set correctly. - */ + /** Asserts that periods' {@link Period#getAdGroupCount()} are set correctly. */ public static void assertAdGroupCounts(Timeline timeline, int... expectedAdGroupCounts) { Period period = new Period(); for (int i = 0; i < timeline.getPeriodCount(); i++) { @@ -166,5 +163,4 @@ public final class TimelineAsserts { assertThat(period.getAdGroupCount()).isEqualTo(expectedAdGroupCounts[i]); } } - }