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/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 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 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/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/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())); } 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/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++) { 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..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; @@ -75,6 +76,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 +266,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 +281,7 @@ public class PlaybackControlView extends FrameLayout { } private void updatePlayPauseButton() { - if (!isVisible() || !isAttachedToWindow()) { + if (!isVisible() || !isAttachedToWindow) { return; } boolean playing = player != null && player.getPlayWhenReady(); @@ -291,7 +293,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 +317,7 @@ public class PlaybackControlView extends FrameLayout { } private void updateProgress() { - if (!isVisible() || !isAttachedToWindow()) { + if (!isVisible() || !isAttachedToWindow) { return; } long duration = player == null ? 0 : player.getDuration(); @@ -350,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; @@ -426,6 +433,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 +448,7 @@ public class PlaybackControlView extends FrameLayout { @Override public void onDetachedFromWindow() { super.onDetachedFromWindow(); + isAttachedToWindow = false; removeCallbacks(updateProgressAction); removeCallbacks(hideAction); } 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 { 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">