From 06fb29c939e74f7198a12bcece7b668df05ee52f Mon Sep 17 00:00:00 2001 From: eguven Date: Mon, 17 Oct 2016 08:13:33 -0700 Subject: [PATCH 01/10] Support caching of multi segment DASH, HLS and Smooth Streaming. Use sha1 of content uri if a custom cache key or content id isn't provided. ------------- Created by MOE: https://github.com/google/moe MOE_MIGRATED_REVID=136351830 --- .../upstream/cache/CacheDataSourceTest.java | 3 +- .../source/ExtractorMediaPeriod.java | 4 +- .../source/dash/DashMediaSource.java | 6 +-- .../source/dash/manifest/Representation.java | 38 +++++++++++-------- .../upstream/cache/CacheDataSource.java | 26 ++++--------- 5 files changed, 34 insertions(+), 43 deletions(-) diff --git a/library/src/androidTest/java/com/google/android/exoplayer2/upstream/cache/CacheDataSourceTest.java b/library/src/androidTest/java/com/google/android/exoplayer2/upstream/cache/CacheDataSourceTest.java index 5e85ad4d4c..d46458db2b 100644 --- a/library/src/androidTest/java/com/google/android/exoplayer2/upstream/cache/CacheDataSourceTest.java +++ b/library/src/androidTest/java/com/google/android/exoplayer2/upstream/cache/CacheDataSourceTest.java @@ -177,8 +177,7 @@ public class CacheDataSourceTest extends InstrumentationTestCase { builder.setSimulateUnknownLength(simulateUnknownLength); builder.appendReadData(TEST_DATA); FakeDataSource upstream = builder.build(); - return new CacheDataSource(simpleCache, upstream, - CacheDataSource.FLAG_BLOCK_ON_CACHE | CacheDataSource.FLAG_CACHE_UNBOUNDED_REQUESTS, + return new CacheDataSource(simpleCache, upstream, CacheDataSource.FLAG_BLOCK_ON_CACHE, MAX_CACHE_FILE_SIZE); } diff --git a/library/src/main/java/com/google/android/exoplayer2/source/ExtractorMediaPeriod.java b/library/src/main/java/com/google/android/exoplayer2/source/ExtractorMediaPeriod.java index 27bd1f677f..18de1b9df9 100644 --- a/library/src/main/java/com/google/android/exoplayer2/source/ExtractorMediaPeriod.java +++ b/library/src/main/java/com/google/android/exoplayer2/source/ExtractorMediaPeriod.java @@ -39,7 +39,6 @@ import com.google.android.exoplayer2.upstream.Loader; import com.google.android.exoplayer2.upstream.Loader.Loadable; import com.google.android.exoplayer2.util.Assertions; import com.google.android.exoplayer2.util.ConditionVariable; -import com.google.android.exoplayer2.util.Util; import java.io.EOFException; import java.io.IOException; @@ -593,8 +592,7 @@ import java.io.IOException; ExtractorInput input = null; try { long position = positionHolder.position; - length = dataSource.open( - new DataSpec(uri, position, C.LENGTH_UNSET, Util.sha1(uri.toString()))); + length = dataSource.open(new DataSpec(uri, position, C.LENGTH_UNSET, null)); if (length != C.LENGTH_UNSET) { length += position; } diff --git a/library/src/main/java/com/google/android/exoplayer2/source/dash/DashMediaSource.java b/library/src/main/java/com/google/android/exoplayer2/source/dash/DashMediaSource.java index 766f1e0ebf..f22d1693a9 100644 --- a/library/src/main/java/com/google/android/exoplayer2/source/dash/DashMediaSource.java +++ b/library/src/main/java/com/google/android/exoplayer2/source/dash/DashMediaSource.java @@ -124,7 +124,7 @@ public final class DashMediaSource implements MediaSource { this.minLoadableRetryCount = minLoadableRetryCount; this.livePresentationDelayMs = livePresentationDelayMs; eventDispatcher = new EventDispatcher(eventHandler, eventListener); - manifestParser = new DashManifestParser(generateContentId()); + manifestParser = new DashManifestParser(); manifestCallback = new ManifestCallback(); manifestUriLock = new Object(); periodsById = new SparseArray<>(); @@ -468,10 +468,6 @@ public final class DashMediaSource implements MediaSource { } } - private String generateContentId() { - return Util.sha1(manifestUri.toString()); - } - private static final class PeriodSeekInfo { public static PeriodSeekInfo createPeriodSeekInfo( diff --git a/library/src/main/java/com/google/android/exoplayer2/source/dash/manifest/Representation.java b/library/src/main/java/com/google/android/exoplayer2/source/dash/manifest/Representation.java index 9c6d2e1582..6ebd69e29b 100644 --- a/library/src/main/java/com/google/android/exoplayer2/source/dash/manifest/Representation.java +++ b/library/src/main/java/com/google/android/exoplayer2/source/dash/manifest/Representation.java @@ -57,7 +57,6 @@ public abstract class Representation { */ public final long presentationTimeOffsetUs; - private final String cacheKey; private final RangedUri initializationUri; /** @@ -81,7 +80,8 @@ public abstract class Representation { * @param revisionId Identifies the revision of the content. * @param format The format of the representation. * @param segmentBase A segment base element for the representation. - * @param customCacheKey A custom value to be returned from {@link #getCacheKey()}, or null. + * @param customCacheKey A custom value to be returned from {@link #getCacheKey()}, or null. This + * parameter is ignored if {@code segmentBase} consists of multiple segments. * @return The constructed instance. */ public static Representation newInstance(String contentId, long revisionId, Format format, @@ -91,7 +91,7 @@ public abstract class Representation { (SingleSegmentBase) segmentBase, customCacheKey, C.LENGTH_UNSET); } else if (segmentBase instanceof MultiSegmentBase) { return new MultiSegmentRepresentation(contentId, revisionId, format, - (MultiSegmentBase) segmentBase, customCacheKey); + (MultiSegmentBase) segmentBase); } else { throw new IllegalArgumentException("segmentBase must be of type SingleSegmentBase or " + "MultiSegmentBase"); @@ -99,12 +99,10 @@ public abstract class Representation { } private Representation(String contentId, long revisionId, Format format, - SegmentBase segmentBase, String customCacheKey) { + SegmentBase segmentBase) { this.contentId = contentId; this.revisionId = revisionId; this.format = format; - this.cacheKey = customCacheKey != null ? customCacheKey - : contentId + "." + format.id + "." + revisionId; initializationUri = segmentBase.getInitialization(this); presentationTimeOffsetUs = segmentBase.getPresentationTimeOffsetUs(); } @@ -129,12 +127,10 @@ public abstract class Representation { public abstract DashSegmentIndex getIndex(); /** - * Returns a cache key for the representation, in the format - * {@code contentId + "." + format.id + "." + revisionId}. + * Returns a cache key for the representation if a custom cache key or content id has been + * provided and there is only single segment. */ - public String getCacheKey() { - return cacheKey; - } + public abstract String getCacheKey(); /** * A DASH representation consisting of a single segment. @@ -151,6 +147,7 @@ public abstract class Representation { */ public final long contentLength; + private final String cacheKey; private final RangedUri indexUri; private final SingleSegmentIndex segmentIndex; @@ -187,9 +184,11 @@ public abstract class Representation { */ public SingleSegmentRepresentation(String contentId, long revisionId, Format format, SingleSegmentBase segmentBase, String customCacheKey, long contentLength) { - super(contentId, revisionId, format, segmentBase, customCacheKey); + super(contentId, revisionId, format, segmentBase); this.uri = Uri.parse(segmentBase.uri); this.indexUri = segmentBase.getIndex(); + this.cacheKey = customCacheKey != null ? customCacheKey + : contentId != null ? contentId + "." + format.id + "." + revisionId : null; this.contentLength = contentLength; // If we have an index uri then the index is defined externally, and we shouldn't return one // directly. If we don't, then we can't do better than an index defining a single segment. @@ -207,6 +206,11 @@ public abstract class Representation { return segmentIndex; } + @Override + public String getCacheKey() { + return cacheKey; + } + } /** @@ -222,11 +226,10 @@ public abstract class Representation { * @param revisionId Identifies the revision of the content. * @param format The format of the representation. * @param segmentBase The segment base underlying the representation. - * @param customCacheKey A custom value to be returned from {@link #getCacheKey()}, or null. */ public MultiSegmentRepresentation(String contentId, long revisionId, Format format, - MultiSegmentBase segmentBase, String customCacheKey) { - super(contentId, revisionId, format, segmentBase, customCacheKey); + MultiSegmentBase segmentBase) { + super(contentId, revisionId, format, segmentBase); this.segmentBase = segmentBase; } @@ -240,6 +243,11 @@ public abstract class Representation { return this; } + @Override + public String getCacheKey() { + return null; + } + // DashSegmentIndex implementation. @Override diff --git a/library/src/main/java/com/google/android/exoplayer2/upstream/cache/CacheDataSource.java b/library/src/main/java/com/google/android/exoplayer2/upstream/cache/CacheDataSource.java index 727eb068ce..1f56d4ef83 100644 --- a/library/src/main/java/com/google/android/exoplayer2/upstream/cache/CacheDataSource.java +++ b/library/src/main/java/com/google/android/exoplayer2/upstream/cache/CacheDataSource.java @@ -26,6 +26,7 @@ import com.google.android.exoplayer2.upstream.DataSpec; import com.google.android.exoplayer2.upstream.FileDataSource; import com.google.android.exoplayer2.upstream.TeeDataSource; import com.google.android.exoplayer2.upstream.cache.CacheDataSink.CacheDataSinkException; +import com.google.android.exoplayer2.util.Util; import java.io.IOException; import java.io.InterruptedIOException; import java.lang.annotation.Retention; @@ -50,8 +51,7 @@ public final class CacheDataSource implements DataSource { * Flags controlling the cache's behavior. */ @Retention(RetentionPolicy.SOURCE) - @IntDef(flag = true, value = {FLAG_BLOCK_ON_CACHE, FLAG_IGNORE_CACHE_ON_ERROR, - FLAG_CACHE_UNBOUNDED_REQUESTS}) + @IntDef(flag = true, value = {FLAG_BLOCK_ON_CACHE, FLAG_IGNORE_CACHE_ON_ERROR}) public @interface Flags {} /** * A flag indicating whether we will block reads if the cache key is locked. If this flag is @@ -66,13 +66,6 @@ public final class CacheDataSource implements DataSource { */ public static final int FLAG_IGNORE_CACHE_ON_ERROR = 1 << 1; - /** - * A flag indicating whether the response is cached if the range of the request is unbounded. - * Disabled by default because, as a side effect, this may allow streams with every chunk from a - * separate URL cached which is broken currently. - */ - public static final int FLAG_CACHE_UNBOUNDED_REQUESTS = 1 << 2; - /** * Listener of {@link CacheDataSource} events. */ @@ -98,7 +91,6 @@ public final class CacheDataSource implements DataSource { private final boolean blockOnCache; private final boolean ignoreCacheOnError; - private final boolean bypassUnboundedRequests; private DataSource currentDataSource; private boolean currentRequestUnbounded; @@ -127,8 +119,8 @@ public final class CacheDataSource implements DataSource { * * @param cache The cache. * @param upstream A {@link DataSource} for reading data not in the cache. - * @param flags A combination of {@link #FLAG_BLOCK_ON_CACHE}, {@link #FLAG_IGNORE_CACHE_ON_ERROR} - * and {@link #FLAG_CACHE_UNBOUNDED_REQUESTS} or 0. + * @param flags A combination of {@link #FLAG_BLOCK_ON_CACHE} and {@link + * #FLAG_IGNORE_CACHE_ON_ERROR} or 0. * @param maxCacheFileSize The maximum size of a cache file, in bytes. If the cached data size * exceeds this value, then the data will be fragmented into multiple cache files. The * finer-grained this is the finer-grained the eviction policy can be. @@ -148,8 +140,8 @@ public final class CacheDataSource implements DataSource { * @param upstream A {@link DataSource} for reading data not in the cache. * @param cacheReadDataSource A {@link DataSource} for reading data from the cache. * @param cacheWriteDataSink A {@link DataSink} for writing data to the cache. - * @param flags A combination of {@link #FLAG_BLOCK_ON_CACHE}, {@link #FLAG_IGNORE_CACHE_ON_ERROR} - * and {@link #FLAG_CACHE_UNBOUNDED_REQUESTS} or 0. + * @param flags A combination of {@link #FLAG_BLOCK_ON_CACHE} and {@link + * #FLAG_IGNORE_CACHE_ON_ERROR} or 0. * @param eventListener An optional {@link EventListener} to receive events. */ public CacheDataSource(Cache cache, DataSource upstream, DataSource cacheReadDataSource, @@ -158,7 +150,6 @@ public final class CacheDataSource implements DataSource { this.cacheReadDataSource = cacheReadDataSource; this.blockOnCache = (flags & FLAG_BLOCK_ON_CACHE) != 0; this.ignoreCacheOnError = (flags & FLAG_IGNORE_CACHE_ON_ERROR) != 0; - this.bypassUnboundedRequests = (flags & FLAG_CACHE_UNBOUNDED_REQUESTS) == 0; this.upstreamDataSource = upstream; if (cacheWriteDataSink != null) { this.cacheWriteDataSource = new TeeDataSource(upstream, cacheWriteDataSink); @@ -173,10 +164,9 @@ public final class CacheDataSource implements DataSource { try { uri = dataSpec.uri; flags = dataSpec.flags; - key = dataSpec.key; + key = dataSpec.key != null ? dataSpec.key : Util.sha1(uri.toString()); readPosition = dataSpec.position; - currentRequestIgnoresCache = (ignoreCacheOnError && seenCacheError) - || (bypassUnboundedRequests && dataSpec.length == C.LENGTH_UNSET); + currentRequestIgnoresCache = ignoreCacheOnError && seenCacheError; if (dataSpec.length != C.LENGTH_UNSET || currentRequestIgnoresCache) { bytesRemaining = dataSpec.length; } else { From d3f78e48081d71b75bedeeeee870972a7f9a24a5 Mon Sep 17 00:00:00 2001 From: kapishnikov Date: Mon, 17 Oct 2016 14:54:17 -0700 Subject: [PATCH 02/10] Deprecate call to new UrlRequest.Builder(...) ------------- Created by MOE: https://github.com/google/moe MOE_MIGRATED_REVID=136399474 --- .../ext/cronet/CronetDataSourceTest.java | 60 ++++--------------- .../ext/cronet/CronetDataSource.java | 4 +- 2 files changed, 14 insertions(+), 50 deletions(-) diff --git a/extensions/cronet/src/androidTest/java/com/google/android/exoplayer2/ext/cronet/CronetDataSourceTest.java b/extensions/cronet/src/androidTest/java/com/google/android/exoplayer2/ext/cronet/CronetDataSourceTest.java index b0de0784de..c214503f0c 100644 --- a/extensions/cronet/src/androidTest/java/com/google/android/exoplayer2/ext/cronet/CronetDataSourceTest.java +++ b/extensions/cronet/src/androidTest/java/com/google/android/exoplayer2/ext/cronet/CronetDataSourceTest.java @@ -22,7 +22,6 @@ import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertTrue; import static org.junit.Assert.fail; import static org.mockito.Matchers.any; -import static org.mockito.Matchers.anyInt; import static org.mockito.Matchers.anyString; import static org.mockito.Matchers.eq; import static org.mockito.Mockito.doAnswer; @@ -52,7 +51,6 @@ import java.net.UnknownHostException; import java.nio.ByteBuffer; import java.util.ArrayList; import java.util.Arrays; -import java.util.Collection; import java.util.Collections; import java.util.HashMap; import java.util.Map; @@ -88,20 +86,7 @@ public final class CronetDataSourceTest { private Map testResponseHeader; private UrlResponseInfo testUrlResponseInfo; - /** - * MockableCronetEngine is an abstract class for helping creating new Requests. - */ - public abstract static class MockableCronetEngine extends CronetEngine { - - @Override - public abstract UrlRequest createRequest(String url, UrlRequest.Callback callback, - Executor executor, int priority, - Collection connectionAnnotations, - boolean disableCache, - boolean disableConnectionMigration, - boolean allowDirectExecutor); - } - + @Mock private UrlRequest.Builder mockUrlRequestBuilder; @Mock private UrlRequest mockUrlRequest; @Mock @@ -114,8 +99,7 @@ public final class CronetDataSourceTest { private Executor mockExecutor; @Mock private UrlRequestException mockUrlRequestException; - @Mock - private MockableCronetEngine mockCronetEngine; + @Mock private CronetEngine mockCronetEngine; private CronetDataSource dataSourceUnderTest; @@ -135,15 +119,10 @@ public final class CronetDataSourceTest { true, // resetTimeoutOnRedirects mockClock)); when(mockContentTypePredicate.evaluate(anyString())).thenReturn(true); - when(mockCronetEngine.createRequest( - anyString(), - any(UrlRequest.Callback.class), - any(Executor.class), - anyInt(), - eq(Collections.emptyList()), - any(Boolean.class), - any(Boolean.class), - any(Boolean.class))).thenReturn(mockUrlRequest); + when(mockCronetEngine.newUrlRequestBuilder( + anyString(), any(UrlRequest.Callback.class), any(Executor.class))) + .thenReturn(mockUrlRequestBuilder); + when(mockUrlRequestBuilder.build()).thenReturn(mockUrlRequest); mockStatusResponse(); testDataSpec = new DataSpec(Uri.parse(TEST_URL), 0, C.LENGTH_UNSET, null); @@ -184,15 +163,7 @@ public final class CronetDataSourceTest { dataSourceUnderTest.close(); // Prepare a mock UrlRequest to be used in the second open() call. final UrlRequest mockUrlRequest2 = mock(UrlRequest.class); - when(mockCronetEngine.createRequest( - anyString(), - any(UrlRequest.Callback.class), - any(Executor.class), - anyInt(), - eq(Collections.emptyList()), - any(Boolean.class), - any(Boolean.class), - any(Boolean.class))).thenReturn(mockUrlRequest2); + when(mockUrlRequestBuilder.build()).thenReturn(mockUrlRequest2); doAnswer(new Answer() { @Override public Object answer(InvocationOnMock invocation) throws Throwable { @@ -215,15 +186,8 @@ public final class CronetDataSourceTest { mockResponseStartSuccess(); dataSourceUnderTest.open(testDataSpec); - verify(mockCronetEngine).createRequest( - eq(TEST_URL), - any(UrlRequest.Callback.class), - any(Executor.class), - anyInt(), - eq(Collections.emptyList()), - any(Boolean.class), - any(Boolean.class), - any(Boolean.class)); + verify(mockCronetEngine) + .newUrlRequestBuilder(eq(TEST_URL), any(UrlRequest.Callback.class), any(Executor.class)); verify(mockUrlRequest).start(); } @@ -237,9 +201,9 @@ public final class CronetDataSourceTest { dataSourceUnderTest.open(testDataSpec); // The header value to add is current position to current position + length - 1. - verify(mockUrlRequest).addHeader("Range", "bytes=1000-5999"); - verify(mockUrlRequest).addHeader("firstHeader", "firstValue"); - verify(mockUrlRequest).addHeader("secondHeader", "secondValue"); + verify(mockUrlRequestBuilder).addHeader("Range", "bytes=1000-5999"); + verify(mockUrlRequestBuilder).addHeader("firstHeader", "firstValue"); + verify(mockUrlRequestBuilder).addHeader("secondHeader", "secondValue"); verify(mockUrlRequest).start(); } diff --git a/extensions/cronet/src/main/java/com/google/android/exoplayer2/ext/cronet/CronetDataSource.java b/extensions/cronet/src/main/java/com/google/android/exoplayer2/ext/cronet/CronetDataSource.java index 0190668a70..83f46bd488 100644 --- a/extensions/cronet/src/main/java/com/google/android/exoplayer2/ext/cronet/CronetDataSource.java +++ b/extensions/cronet/src/main/java/com/google/android/exoplayer2/ext/cronet/CronetDataSource.java @@ -412,8 +412,8 @@ public class CronetDataSource extends UrlRequest.Callback implements HttpDataSou // Internal methods. private UrlRequest buildRequest(DataSpec dataSpec) throws OpenException { - UrlRequest.Builder requestBuilder = new UrlRequest.Builder(dataSpec.uri.toString(), this, - executor, cronetEngine); + UrlRequest.Builder requestBuilder = cronetEngine.newUrlRequestBuilder(dataSpec.uri.toString(), + this, executor); // Set the headers. synchronized (requestProperties) { if (dataSpec.postBody != null && dataSpec.postBody.length != 0 From 1cfc432bb8a9db2feaf72b226a6cc0efceb881a6 Mon Sep 17 00:00:00 2001 From: olly Date: Tue, 18 Oct 2016 11:53:00 -0700 Subject: [PATCH 03/10] Add REQUIRE_HTTPS flag Note that it's not possible for the library to enforce that the flag is adhered to, since it's possible for applications to inject custom implementations of DataSource (there's no requirement they even extend HttpDataSource for network requesting implementations). It's possible for applications to replace pretty much anything in the library, so there's no other place we could put the flag where we could make this guarantee. Hence this is a best-effort that will work when using HttpDataSource implementations that we provide. ------------- Created by MOE: https://github.com/google/moe MOE_MIGRATED_REVID=136500947 --- .../ext/cronet/CronetDataSource.java | 3 +- .../ext/okhttp/OkHttpDataSource.java | 3 ++ .../android/exoplayer2/ExoPlayerFlags.java | 32 +++++++++++++++++++ .../android/exoplayer2/upstream/DataSpec.java | 7 ++++ .../upstream/DefaultHttpDataSource.java | 3 ++ 5 files changed, 47 insertions(+), 1 deletion(-) create mode 100644 library/src/main/java/com/google/android/exoplayer2/ExoPlayerFlags.java diff --git a/extensions/cronet/src/main/java/com/google/android/exoplayer2/ext/cronet/CronetDataSource.java b/extensions/cronet/src/main/java/com/google/android/exoplayer2/ext/cronet/CronetDataSource.java index 83f46bd488..e75524c1c9 100644 --- a/extensions/cronet/src/main/java/com/google/android/exoplayer2/ext/cronet/CronetDataSource.java +++ b/extensions/cronet/src/main/java/com/google/android/exoplayer2/ext/cronet/CronetDataSource.java @@ -20,6 +20,7 @@ import android.os.ConditionVariable; import android.text.TextUtils; import android.util.Log; import com.google.android.exoplayer2.C; +import com.google.android.exoplayer2.ExoPlayerFlags; import com.google.android.exoplayer2.upstream.DataSourceException; import com.google.android.exoplayer2.upstream.DataSpec; import com.google.android.exoplayer2.upstream.HttpDataSource; @@ -206,7 +207,7 @@ public class CronetDataSource extends UrlRequest.Callback implements HttpDataSou @Override public long open(DataSpec dataSpec) throws HttpDataSourceException { - Assertions.checkNotNull(dataSpec); + Assertions.checkState(!ExoPlayerFlags.REQUIRE_HTTPS || dataSpec.isHttps()); Assertions.checkState(!opened); operation.close(); diff --git a/extensions/okhttp/src/main/java/com/google/android/exoplayer2/ext/okhttp/OkHttpDataSource.java b/extensions/okhttp/src/main/java/com/google/android/exoplayer2/ext/okhttp/OkHttpDataSource.java index 2b6eaa736d..07c607771a 100644 --- a/extensions/okhttp/src/main/java/com/google/android/exoplayer2/ext/okhttp/OkHttpDataSource.java +++ b/extensions/okhttp/src/main/java/com/google/android/exoplayer2/ext/okhttp/OkHttpDataSource.java @@ -17,6 +17,7 @@ package com.google.android.exoplayer2.ext.okhttp; import android.net.Uri; import com.google.android.exoplayer2.C; +import com.google.android.exoplayer2.ExoPlayerFlags; import com.google.android.exoplayer2.upstream.DataSourceException; import com.google.android.exoplayer2.upstream.DataSpec; import com.google.android.exoplayer2.upstream.HttpDataSource; @@ -145,6 +146,8 @@ public class OkHttpDataSource implements HttpDataSource { @Override public long open(DataSpec dataSpec) throws HttpDataSourceException { + Assertions.checkState(!ExoPlayerFlags.REQUIRE_HTTPS || dataSpec.isHttps()); + this.dataSpec = dataSpec; this.bytesRead = 0; this.bytesSkipped = 0; diff --git a/library/src/main/java/com/google/android/exoplayer2/ExoPlayerFlags.java b/library/src/main/java/com/google/android/exoplayer2/ExoPlayerFlags.java new file mode 100644 index 0000000000..0af9208d59 --- /dev/null +++ b/library/src/main/java/com/google/android/exoplayer2/ExoPlayerFlags.java @@ -0,0 +1,32 @@ +/* + * 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; + +/** + * Global configuration flags. Applications may toggle these flags, but should do so prior to any + * other use of the library. + */ +public final class ExoPlayerFlags { + + /** + * If set, indicates to {@link com.google.android.exoplayer2.upstream.HttpDataSource} + * implementations that they should reject non-HTTPS requests. + */ + public static boolean REQUIRE_HTTPS = false; + + private ExoPlayerFlags() {} + +} diff --git a/library/src/main/java/com/google/android/exoplayer2/upstream/DataSpec.java b/library/src/main/java/com/google/android/exoplayer2/upstream/DataSpec.java index d251446976..ced276d7eb 100644 --- a/library/src/main/java/com/google/android/exoplayer2/upstream/DataSpec.java +++ b/library/src/main/java/com/google/android/exoplayer2/upstream/DataSpec.java @@ -167,6 +167,13 @@ public final class DataSpec { this.flags = flags; } + /** + * Returns whether the instance defines a HTTPS request. + */ + public boolean isHttps() { + return uri != null && "https".equalsIgnoreCase(uri.getScheme()); + } + @Override public String toString() { return "DataSpec[" + uri + ", " + Arrays.toString(postBody) + ", " + absoluteStreamPosition diff --git a/library/src/main/java/com/google/android/exoplayer2/upstream/DefaultHttpDataSource.java b/library/src/main/java/com/google/android/exoplayer2/upstream/DefaultHttpDataSource.java index b326c41b18..2b3f91c77c 100644 --- a/library/src/main/java/com/google/android/exoplayer2/upstream/DefaultHttpDataSource.java +++ b/library/src/main/java/com/google/android/exoplayer2/upstream/DefaultHttpDataSource.java @@ -19,6 +19,7 @@ import android.net.Uri; import android.text.TextUtils; import android.util.Log; import com.google.android.exoplayer2.C; +import com.google.android.exoplayer2.ExoPlayerFlags; import com.google.android.exoplayer2.util.Assertions; import com.google.android.exoplayer2.util.Predicate; import com.google.android.exoplayer2.util.Util; @@ -186,6 +187,8 @@ public class DefaultHttpDataSource implements HttpDataSource { @Override public long open(DataSpec dataSpec) throws HttpDataSourceException { + Assertions.checkState(!ExoPlayerFlags.REQUIRE_HTTPS || dataSpec.isHttps()); + this.dataSpec = dataSpec; this.bytesRead = 0; this.bytesSkipped = 0; From aecbbdd36c669f1c98facac91b92eb07850bf38c Mon Sep 17 00:00:00 2001 From: aquilescanta Date: Tue, 18 Oct 2016 11:55:47 -0700 Subject: [PATCH 04/10] Replace IndexOutOfBounds Exception for BehindLiveWindowException This is a problem when two invocations of getNextChunk that retrieve chunks (i.e. not playlists) occur too apart in time. In that case the last loaded chunk has a media sequence that is behind the current playlist. ------------- Created by MOE: https://github.com/google/moe MOE_MIGRATED_REVID=136501291 --- .../google/android/exoplayer2/source/hls/HlsChunkSource.java | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/library/src/main/java/com/google/android/exoplayer2/source/hls/HlsChunkSource.java b/library/src/main/java/com/google/android/exoplayer2/source/hls/HlsChunkSource.java index 53d9e70d76..a23ab3bae7 100644 --- a/library/src/main/java/com/google/android/exoplayer2/source/hls/HlsChunkSource.java +++ b/library/src/main/java/com/google/android/exoplayer2/source/hls/HlsChunkSource.java @@ -440,6 +440,10 @@ import java.util.Locale; } HlsMediaPlaylist oldMediaPlaylist = variantPlaylists[oldVariantIndex]; HlsMediaPlaylist newMediaPlaylist = variantPlaylists[newVariantIndex]; + if (previousChunkIndex < oldMediaPlaylist.mediaSequence) { + // We have fallen behind the live window. + return newMediaPlaylist.mediaSequence - 1; + } double offsetToLiveInstantSecs = 0; for (int i = previousChunkIndex - oldMediaPlaylist.mediaSequence; i < oldMediaPlaylist.segments.size(); i++) { From aa1002a0d616b2ce50ce52d2d18e5c223a1365cd Mon Sep 17 00:00:00 2001 From: olly Date: Wed, 19 Oct 2016 05:16:36 -0700 Subject: [PATCH 05/10] Rollback "Add REQUIRE_HTTPS flag" *** Reason for rollback *** Flag doesn't enforce what it says it enforces, due to redirects *** Original change description *** Add REQUIRE_HTTPS flag Note that it's not possible for the library to enforce that the flag is adhered to, since it's possible for applications to inject custom implementations of DataSource (there's no requirement they even extend HttpDataSource for network requesting implementations). It's possible for applications to replace pretty much anything in the library, so there's no other place we could put the flag where we could make this guarantee. Hence this is a best-effort that will work when... *** ------------- Created by MOE: https://github.com/google/moe MOE_MIGRATED_REVID=136583459 --- .../ext/cronet/CronetDataSource.java | 3 +- .../ext/okhttp/OkHttpDataSource.java | 3 -- .../android/exoplayer2/ExoPlayerFlags.java | 32 ------------------- .../android/exoplayer2/upstream/DataSpec.java | 7 ---- .../upstream/DefaultHttpDataSource.java | 3 -- 5 files changed, 1 insertion(+), 47 deletions(-) delete mode 100644 library/src/main/java/com/google/android/exoplayer2/ExoPlayerFlags.java diff --git a/extensions/cronet/src/main/java/com/google/android/exoplayer2/ext/cronet/CronetDataSource.java b/extensions/cronet/src/main/java/com/google/android/exoplayer2/ext/cronet/CronetDataSource.java index e75524c1c9..83f46bd488 100644 --- a/extensions/cronet/src/main/java/com/google/android/exoplayer2/ext/cronet/CronetDataSource.java +++ b/extensions/cronet/src/main/java/com/google/android/exoplayer2/ext/cronet/CronetDataSource.java @@ -20,7 +20,6 @@ import android.os.ConditionVariable; import android.text.TextUtils; import android.util.Log; import com.google.android.exoplayer2.C; -import com.google.android.exoplayer2.ExoPlayerFlags; import com.google.android.exoplayer2.upstream.DataSourceException; import com.google.android.exoplayer2.upstream.DataSpec; import com.google.android.exoplayer2.upstream.HttpDataSource; @@ -207,7 +206,7 @@ public class CronetDataSource extends UrlRequest.Callback implements HttpDataSou @Override public long open(DataSpec dataSpec) throws HttpDataSourceException { - Assertions.checkState(!ExoPlayerFlags.REQUIRE_HTTPS || dataSpec.isHttps()); + Assertions.checkNotNull(dataSpec); Assertions.checkState(!opened); operation.close(); diff --git a/extensions/okhttp/src/main/java/com/google/android/exoplayer2/ext/okhttp/OkHttpDataSource.java b/extensions/okhttp/src/main/java/com/google/android/exoplayer2/ext/okhttp/OkHttpDataSource.java index 07c607771a..2b6eaa736d 100644 --- a/extensions/okhttp/src/main/java/com/google/android/exoplayer2/ext/okhttp/OkHttpDataSource.java +++ b/extensions/okhttp/src/main/java/com/google/android/exoplayer2/ext/okhttp/OkHttpDataSource.java @@ -17,7 +17,6 @@ package com.google.android.exoplayer2.ext.okhttp; import android.net.Uri; import com.google.android.exoplayer2.C; -import com.google.android.exoplayer2.ExoPlayerFlags; import com.google.android.exoplayer2.upstream.DataSourceException; import com.google.android.exoplayer2.upstream.DataSpec; import com.google.android.exoplayer2.upstream.HttpDataSource; @@ -146,8 +145,6 @@ public class OkHttpDataSource implements HttpDataSource { @Override public long open(DataSpec dataSpec) throws HttpDataSourceException { - Assertions.checkState(!ExoPlayerFlags.REQUIRE_HTTPS || dataSpec.isHttps()); - this.dataSpec = dataSpec; this.bytesRead = 0; this.bytesSkipped = 0; diff --git a/library/src/main/java/com/google/android/exoplayer2/ExoPlayerFlags.java b/library/src/main/java/com/google/android/exoplayer2/ExoPlayerFlags.java deleted file mode 100644 index 0af9208d59..0000000000 --- a/library/src/main/java/com/google/android/exoplayer2/ExoPlayerFlags.java +++ /dev/null @@ -1,32 +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; - -/** - * Global configuration flags. Applications may toggle these flags, but should do so prior to any - * other use of the library. - */ -public final class ExoPlayerFlags { - - /** - * If set, indicates to {@link com.google.android.exoplayer2.upstream.HttpDataSource} - * implementations that they should reject non-HTTPS requests. - */ - public static boolean REQUIRE_HTTPS = false; - - private ExoPlayerFlags() {} - -} diff --git a/library/src/main/java/com/google/android/exoplayer2/upstream/DataSpec.java b/library/src/main/java/com/google/android/exoplayer2/upstream/DataSpec.java index ced276d7eb..d251446976 100644 --- a/library/src/main/java/com/google/android/exoplayer2/upstream/DataSpec.java +++ b/library/src/main/java/com/google/android/exoplayer2/upstream/DataSpec.java @@ -167,13 +167,6 @@ public final class DataSpec { this.flags = flags; } - /** - * Returns whether the instance defines a HTTPS request. - */ - public boolean isHttps() { - return uri != null && "https".equalsIgnoreCase(uri.getScheme()); - } - @Override public String toString() { return "DataSpec[" + uri + ", " + Arrays.toString(postBody) + ", " + absoluteStreamPosition diff --git a/library/src/main/java/com/google/android/exoplayer2/upstream/DefaultHttpDataSource.java b/library/src/main/java/com/google/android/exoplayer2/upstream/DefaultHttpDataSource.java index 2b3f91c77c..b326c41b18 100644 --- a/library/src/main/java/com/google/android/exoplayer2/upstream/DefaultHttpDataSource.java +++ b/library/src/main/java/com/google/android/exoplayer2/upstream/DefaultHttpDataSource.java @@ -19,7 +19,6 @@ import android.net.Uri; import android.text.TextUtils; import android.util.Log; import com.google.android.exoplayer2.C; -import com.google.android.exoplayer2.ExoPlayerFlags; import com.google.android.exoplayer2.util.Assertions; import com.google.android.exoplayer2.util.Predicate; import com.google.android.exoplayer2.util.Util; @@ -187,8 +186,6 @@ public class DefaultHttpDataSource implements HttpDataSource { @Override public long open(DataSpec dataSpec) throws HttpDataSourceException { - Assertions.checkState(!ExoPlayerFlags.REQUIRE_HTTPS || dataSpec.isHttps()); - this.dataSpec = dataSpec; this.bytesRead = 0; this.bytesSkipped = 0; From 3a5cb435412d6acce4407e7b6fae170b006cb00e Mon Sep 17 00:00:00 2001 From: olly Date: Wed, 19 Oct 2016 07:59:35 -0700 Subject: [PATCH 06/10] Fix use of API level 19 method Issue: #1965 ------------- Created by MOE: https://github.com/google/moe MOE_MIGRATED_REVID=136595233 --- .../android/exoplayer2/ui/PlaybackControlView.java | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/library/src/main/java/com/google/android/exoplayer2/ui/PlaybackControlView.java b/library/src/main/java/com/google/android/exoplayer2/ui/PlaybackControlView.java index 3823f1760e..096e67ec01 100644 --- a/library/src/main/java/com/google/android/exoplayer2/ui/PlaybackControlView.java +++ b/library/src/main/java/com/google/android/exoplayer2/ui/PlaybackControlView.java @@ -75,6 +75,7 @@ public class PlaybackControlView extends FrameLayout { private ExoPlayer player; private VisibilityListener visibilityListener; + private boolean isAttachedToWindow; private boolean dragging; private int rewindMs; private int fastForwardMs; @@ -264,7 +265,7 @@ public class PlaybackControlView extends FrameLayout { removeCallbacks(hideAction); if (showTimeoutMs > 0) { hideAtMs = SystemClock.uptimeMillis() + showTimeoutMs; - if (isAttachedToWindow()) { + if (isAttachedToWindow) { postDelayed(hideAction, showTimeoutMs); } } else { @@ -279,7 +280,7 @@ public class PlaybackControlView extends FrameLayout { } private void updatePlayPauseButton() { - if (!isVisible() || !isAttachedToWindow()) { + if (!isVisible() || !isAttachedToWindow) { return; } boolean playing = player != null && player.getPlayWhenReady(); @@ -291,7 +292,7 @@ public class PlaybackControlView extends FrameLayout { } private void updateNavigation() { - if (!isVisible() || !isAttachedToWindow()) { + if (!isVisible() || !isAttachedToWindow) { return; } Timeline currentTimeline = player != null ? player.getCurrentTimeline() : null; @@ -315,7 +316,7 @@ public class PlaybackControlView extends FrameLayout { } private void updateProgress() { - if (!isVisible() || !isAttachedToWindow()) { + if (!isVisible() || !isAttachedToWindow) { return; } long duration = player == null ? 0 : player.getDuration(); @@ -426,6 +427,7 @@ public class PlaybackControlView extends FrameLayout { @Override public void onAttachedToWindow() { super.onAttachedToWindow(); + isAttachedToWindow = true; if (hideAtMs != C.TIME_UNSET) { long delayMs = hideAtMs - SystemClock.uptimeMillis(); if (delayMs <= 0) { @@ -440,6 +442,7 @@ public class PlaybackControlView extends FrameLayout { @Override public void onDetachedFromWindow() { super.onDetachedFromWindow(); + isAttachedToWindow = false; removeCallbacks(updateProgressAction); removeCallbacks(hideAction); } From 586e6257cd009d137e1fd9250f45036d3f06145a Mon Sep 17 00:00:00 2001 From: olly Date: Wed, 19 Oct 2016 08:18:48 -0700 Subject: [PATCH 07/10] Add explicit TargetApi annotation to remove lint error ------------- Created by MOE: https://github.com/google/moe MOE_MIGRATED_REVID=136597149 --- .../google/android/exoplayer2/ui/PlaybackControlView.java | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/library/src/main/java/com/google/android/exoplayer2/ui/PlaybackControlView.java b/library/src/main/java/com/google/android/exoplayer2/ui/PlaybackControlView.java index 096e67ec01..89c778d072 100644 --- a/library/src/main/java/com/google/android/exoplayer2/ui/PlaybackControlView.java +++ b/library/src/main/java/com/google/android/exoplayer2/ui/PlaybackControlView.java @@ -15,6 +15,7 @@ */ package com.google.android.exoplayer2.ui; +import android.annotation.TargetApi; import android.content.Context; import android.content.res.TypedArray; import android.os.SystemClock; @@ -351,13 +352,18 @@ public class PlaybackControlView extends FrameLayout { private void setButtonEnabled(boolean enabled, View view) { view.setEnabled(enabled); if (Util.SDK_INT >= 11) { - view.setAlpha(enabled ? 1f : 0.3f); + setViewAlphaV11(view, enabled ? 1f : 0.3f); view.setVisibility(VISIBLE); } else { view.setVisibility(enabled ? VISIBLE : INVISIBLE); } } + @TargetApi(11) + private void setViewAlphaV11(View view, float alpha) { + view.setAlpha(alpha); + } + private String stringForTime(long timeMs) { if (timeMs == C.TIME_UNSET) { timeMs = 0; From aa660acf0300d0ac22f54bd3ba80ceab77f4b21e Mon Sep 17 00:00:00 2001 From: Oliver Woodman Date: Thu, 20 Oct 2016 12:00:35 +0100 Subject: [PATCH 08/10] Fix typo in comments --- .../android/exoplayer2/ext/okhttp/OkHttpDataSource.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/extensions/okhttp/src/main/java/com/google/android/exoplayer2/ext/okhttp/OkHttpDataSource.java b/extensions/okhttp/src/main/java/com/google/android/exoplayer2/ext/okhttp/OkHttpDataSource.java index 2b6eaa736d..e56072f368 100644 --- a/extensions/okhttp/src/main/java/com/google/android/exoplayer2/ext/okhttp/OkHttpDataSource.java +++ b/extensions/okhttp/src/main/java/com/google/android/exoplayer2/ext/okhttp/OkHttpDataSource.java @@ -65,7 +65,7 @@ public class OkHttpDataSource implements HttpDataSource { private long bytesRead; /** - * @param callFactory An {@link Call.Factory} for use by the source. + * @param callFactory A {@link Call.Factory} for use by the source. * @param userAgent The User-Agent string that should be used. * @param contentTypePredicate An optional {@link Predicate}. If a content type is rejected by the * predicate then a InvalidContentTypeException} is thrown from {@link #open(DataSpec)}. @@ -76,7 +76,7 @@ public class OkHttpDataSource implements HttpDataSource { } /** - * @param callFactory An {@link Call.Factory} for use by the source. + * @param callFactory A {@link Call.Factory} for use by the source. * @param userAgent The User-Agent string that should be used. * @param contentTypePredicate An optional {@link Predicate}. If a content type is rejected by the * predicate then a {@link InvalidContentTypeException} is thrown from From bebbf29a786f98f5ffd37f072c9b44974ae60e5d Mon Sep 17 00:00:00 2001 From: aquilescanta Date: Wed, 19 Oct 2016 09:58:41 -0700 Subject: [PATCH 09/10] Fix NPE when trying to play H265 in Ts files ------------- Created by MOE: https://github.com/google/moe MOE_MIGRATED_REVID=136607848 --- .../google/android/exoplayer2/extractor/ts/H265Reader.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/library/src/main/java/com/google/android/exoplayer2/extractor/ts/H265Reader.java b/library/src/main/java/com/google/android/exoplayer2/extractor/ts/H265Reader.java index 6283371a19..57d7e77bb7 100644 --- a/library/src/main/java/com/google/android/exoplayer2/extractor/ts/H265Reader.java +++ b/library/src/main/java/com/google/android/exoplayer2/extractor/ts/H265Reader.java @@ -44,6 +44,7 @@ import java.util.Collections; private static final int SUFFIX_SEI_NUT = 40; private TrackOutput output; + private SampleReader sampleReader; private SeiReader seiReader; // State that should not be reset on seek. @@ -56,7 +57,6 @@ import java.util.Collections; private final NalUnitTargetBuffer pps; private final NalUnitTargetBuffer prefixSei; private final NalUnitTargetBuffer suffixSei; // TODO: Are both needed? - private final SampleReader sampleReader; private long totalBytesWritten; // Per packet state that gets reset at the start of each packet. @@ -72,7 +72,6 @@ import java.util.Collections; pps = new NalUnitTargetBuffer(PPS_NUT, 128); prefixSei = new NalUnitTargetBuffer(PREFIX_SEI_NUT, 128); suffixSei = new NalUnitTargetBuffer(SUFFIX_SEI_NUT, 128); - sampleReader = new SampleReader(output); seiWrapper = new ParsableByteArray(); } @@ -91,6 +90,7 @@ import java.util.Collections; @Override public void init(ExtractorOutput extractorOutput, TrackIdGenerator idGenerator) { output = extractorOutput.track(idGenerator.getNextId()); + sampleReader = new SampleReader(output); seiReader = new SeiReader(extractorOutput.track(idGenerator.getNextId())); } From f56b05f0d73f6ff0b646002c2a8d0acfd21fe723 Mon Sep 17 00:00:00 2001 From: olly Date: Thu, 20 Oct 2016 03:35:32 -0700 Subject: [PATCH 10/10] Bump version to r2.0.4 ------------- Created by MOE: https://github.com/google/moe MOE_MIGRATED_REVID=136697697 --- RELEASENOTES.md | 21 ++++++++++++------- build.gradle | 2 +- demo/src/main/AndroidManifest.xml | 4 ++-- .../exoplayer2/ExoPlayerLibraryInfo.java | 4 ++-- playbacktests/src/main/AndroidManifest.xml | 4 ++-- 5 files changed, 20 insertions(+), 15 deletions(-) diff --git a/RELEASENOTES.md b/RELEASENOTES.md index 9e0439dd12..ce002238ef 100644 --- a/RELEASENOTES.md +++ b/RELEASENOTES.md @@ -1,22 +1,27 @@ # Release notes # +### r2.0.4 ### + +This release contains important bug fixes. Users of earlier r2.0.x versions +should proactively update to this version. + +* Fix crash on Jellybean devices when using playback controls + ([#1965](https://github.com/google/ExoPlayer/issues/1965)). + ### r2.0.3 ### -This release contains important bug fixes. Users of r2.0.0, r2.0.1 and r2.0.2 -should proactively update to this version. - * Fixed NullPointerException in ExtractorMediaSource - ([#1914](https://github.com/google/ExoPlayer/issues/1914). + ([#1914](https://github.com/google/ExoPlayer/issues/1914)). * Fixed NullPointerException in HlsMediaPeriod - ([#1907](https://github.com/google/ExoPlayer/issues/1907). + ([#1907](https://github.com/google/ExoPlayer/issues/1907)). * Fixed memory leak in PlaybackControlView - ([#1908](https://github.com/google/ExoPlayer/issues/1908). + ([#1908](https://github.com/google/ExoPlayer/issues/1908)). * Fixed strict mode violation when using SimpleExoPlayer.setVideoPlayerTextureView(). * Fixed L3 Widevine provisioning - ([#1925](https://github.com/google/ExoPlayer/issues/1925). + ([#1925](https://github.com/google/ExoPlayer/issues/1925)). * Fixed hiding of controls with use_controller="false" - ([#1919](https://github.com/google/ExoPlayer/issues/1919). + ([#1919](https://github.com/google/ExoPlayer/issues/1919)). * Improvements to Cronet network stack extension. * Misc bug fixes. diff --git a/build.gradle b/build.gradle index c50dd31b27..8e9032be70 100644 --- a/build.gradle +++ b/build.gradle @@ -35,7 +35,7 @@ allprojects { releaseRepoName = 'exoplayer' releaseUserOrg = 'google' releaseGroupId = 'com.google.android.exoplayer' - releaseVersion = 'r2.0.3' + releaseVersion = 'r2.0.4' releaseWebsite = 'https://github.com/google/ExoPlayer' } } diff --git a/demo/src/main/AndroidManifest.xml b/demo/src/main/AndroidManifest.xml index 7fc0ac3d9c..1f015827c9 100644 --- a/demo/src/main/AndroidManifest.xml +++ b/demo/src/main/AndroidManifest.xml @@ -16,8 +16,8 @@ + android:versionCode="2004" + android:versionName="2.0.4"> diff --git a/library/src/main/java/com/google/android/exoplayer2/ExoPlayerLibraryInfo.java b/library/src/main/java/com/google/android/exoplayer2/ExoPlayerLibraryInfo.java index 23e6d4d593..02c70bb0be 100644 --- a/library/src/main/java/com/google/android/exoplayer2/ExoPlayerLibraryInfo.java +++ b/library/src/main/java/com/google/android/exoplayer2/ExoPlayerLibraryInfo.java @@ -23,7 +23,7 @@ public interface ExoPlayerLibraryInfo { /** * The version of the library, expressed as a string. */ - String VERSION = "2.0.3"; + String VERSION = "2.0.4"; /** * The version of the library, expressed as an integer. @@ -32,7 +32,7 @@ public interface ExoPlayerLibraryInfo { * corresponding integer version 1002003 (001-002-003), and "123.45.6" has the corresponding * integer version 123045006 (123-045-006). */ - int VERSION_INT = 2000003; + int VERSION_INT = 2000004; /** * Whether the library was compiled with {@link com.google.android.exoplayer2.util.Assertions} diff --git a/playbacktests/src/main/AndroidManifest.xml b/playbacktests/src/main/AndroidManifest.xml index 58ede793b2..6a10654af7 100644 --- a/playbacktests/src/main/AndroidManifest.xml +++ b/playbacktests/src/main/AndroidManifest.xml @@ -17,8 +17,8 @@ + android:versionCode="2004" + android:versionName="2.0.4">