Merge pull request #1971 from google/dev-v2

Update dev-v2-id3
This commit is contained in:
ojw28 2016-10-20 12:43:38 +01:00 committed by GitHub
commit 6673483aca
16 changed files with 90 additions and 117 deletions

View file

@ -1,22 +1,27 @@
# Release notes # # 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 ### ### 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 * Fixed NullPointerException in ExtractorMediaSource
([#1914](https://github.com/google/ExoPlayer/issues/1914). ([#1914](https://github.com/google/ExoPlayer/issues/1914)).
* Fixed NullPointerException in HlsMediaPeriod * 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 * 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 * Fixed strict mode violation when using
SimpleExoPlayer.setVideoPlayerTextureView(). SimpleExoPlayer.setVideoPlayerTextureView().
* Fixed L3 Widevine provisioning * 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" * 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. * Improvements to Cronet network stack extension.
* Misc bug fixes. * Misc bug fixes.

View file

@ -35,7 +35,7 @@ allprojects {
releaseRepoName = 'exoplayer' releaseRepoName = 'exoplayer'
releaseUserOrg = 'google' releaseUserOrg = 'google'
releaseGroupId = 'com.google.android.exoplayer' releaseGroupId = 'com.google.android.exoplayer'
releaseVersion = 'r2.0.3' releaseVersion = 'r2.0.4'
releaseWebsite = 'https://github.com/google/ExoPlayer' releaseWebsite = 'https://github.com/google/ExoPlayer'
} }
} }

View file

@ -16,8 +16,8 @@
<manifest xmlns:android="http://schemas.android.com/apk/res/android" <manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.google.android.exoplayer2.demo" package="com.google.android.exoplayer2.demo"
android:versionCode="2003" android:versionCode="2004"
android:versionName="2.0.3"> android:versionName="2.0.4">
<uses-permission android:name="android.permission.INTERNET"/> <uses-permission android:name="android.permission.INTERNET"/>
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"/> <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"/>

View file

@ -22,7 +22,6 @@ import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue; import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail; import static org.junit.Assert.fail;
import static org.mockito.Matchers.any; import static org.mockito.Matchers.any;
import static org.mockito.Matchers.anyInt;
import static org.mockito.Matchers.anyString; import static org.mockito.Matchers.anyString;
import static org.mockito.Matchers.eq; import static org.mockito.Matchers.eq;
import static org.mockito.Mockito.doAnswer; import static org.mockito.Mockito.doAnswer;
@ -52,7 +51,6 @@ import java.net.UnknownHostException;
import java.nio.ByteBuffer; import java.nio.ByteBuffer;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Arrays; import java.util.Arrays;
import java.util.Collection;
import java.util.Collections; import java.util.Collections;
import java.util.HashMap; import java.util.HashMap;
import java.util.Map; import java.util.Map;
@ -88,20 +86,7 @@ public final class CronetDataSourceTest {
private Map<String, String> testResponseHeader; private Map<String, String> testResponseHeader;
private UrlResponseInfo testUrlResponseInfo; private UrlResponseInfo testUrlResponseInfo;
/** @Mock private UrlRequest.Builder mockUrlRequestBuilder;
* 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<Object> connectionAnnotations,
boolean disableCache,
boolean disableConnectionMigration,
boolean allowDirectExecutor);
}
@Mock @Mock
private UrlRequest mockUrlRequest; private UrlRequest mockUrlRequest;
@Mock @Mock
@ -114,8 +99,7 @@ public final class CronetDataSourceTest {
private Executor mockExecutor; private Executor mockExecutor;
@Mock @Mock
private UrlRequestException mockUrlRequestException; private UrlRequestException mockUrlRequestException;
@Mock @Mock private CronetEngine mockCronetEngine;
private MockableCronetEngine mockCronetEngine;
private CronetDataSource dataSourceUnderTest; private CronetDataSource dataSourceUnderTest;
@ -135,15 +119,10 @@ public final class CronetDataSourceTest {
true, // resetTimeoutOnRedirects true, // resetTimeoutOnRedirects
mockClock)); mockClock));
when(mockContentTypePredicate.evaluate(anyString())).thenReturn(true); when(mockContentTypePredicate.evaluate(anyString())).thenReturn(true);
when(mockCronetEngine.createRequest( when(mockCronetEngine.newUrlRequestBuilder(
anyString(), anyString(), any(UrlRequest.Callback.class), any(Executor.class)))
any(UrlRequest.Callback.class), .thenReturn(mockUrlRequestBuilder);
any(Executor.class), when(mockUrlRequestBuilder.build()).thenReturn(mockUrlRequest);
anyInt(),
eq(Collections.emptyList()),
any(Boolean.class),
any(Boolean.class),
any(Boolean.class))).thenReturn(mockUrlRequest);
mockStatusResponse(); mockStatusResponse();
testDataSpec = new DataSpec(Uri.parse(TEST_URL), 0, C.LENGTH_UNSET, null); testDataSpec = new DataSpec(Uri.parse(TEST_URL), 0, C.LENGTH_UNSET, null);
@ -184,15 +163,7 @@ public final class CronetDataSourceTest {
dataSourceUnderTest.close(); dataSourceUnderTest.close();
// Prepare a mock UrlRequest to be used in the second open() call. // Prepare a mock UrlRequest to be used in the second open() call.
final UrlRequest mockUrlRequest2 = mock(UrlRequest.class); final UrlRequest mockUrlRequest2 = mock(UrlRequest.class);
when(mockCronetEngine.createRequest( when(mockUrlRequestBuilder.build()).thenReturn(mockUrlRequest2);
anyString(),
any(UrlRequest.Callback.class),
any(Executor.class),
anyInt(),
eq(Collections.emptyList()),
any(Boolean.class),
any(Boolean.class),
any(Boolean.class))).thenReturn(mockUrlRequest2);
doAnswer(new Answer<Object>() { doAnswer(new Answer<Object>() {
@Override @Override
public Object answer(InvocationOnMock invocation) throws Throwable { public Object answer(InvocationOnMock invocation) throws Throwable {
@ -215,15 +186,8 @@ public final class CronetDataSourceTest {
mockResponseStartSuccess(); mockResponseStartSuccess();
dataSourceUnderTest.open(testDataSpec); dataSourceUnderTest.open(testDataSpec);
verify(mockCronetEngine).createRequest( verify(mockCronetEngine)
eq(TEST_URL), .newUrlRequestBuilder(eq(TEST_URL), any(UrlRequest.Callback.class), any(Executor.class));
any(UrlRequest.Callback.class),
any(Executor.class),
anyInt(),
eq(Collections.emptyList()),
any(Boolean.class),
any(Boolean.class),
any(Boolean.class));
verify(mockUrlRequest).start(); verify(mockUrlRequest).start();
} }
@ -237,9 +201,9 @@ public final class CronetDataSourceTest {
dataSourceUnderTest.open(testDataSpec); dataSourceUnderTest.open(testDataSpec);
// The header value to add is current position to current position + length - 1. // The header value to add is current position to current position + length - 1.
verify(mockUrlRequest).addHeader("Range", "bytes=1000-5999"); verify(mockUrlRequestBuilder).addHeader("Range", "bytes=1000-5999");
verify(mockUrlRequest).addHeader("firstHeader", "firstValue"); verify(mockUrlRequestBuilder).addHeader("firstHeader", "firstValue");
verify(mockUrlRequest).addHeader("secondHeader", "secondValue"); verify(mockUrlRequestBuilder).addHeader("secondHeader", "secondValue");
verify(mockUrlRequest).start(); verify(mockUrlRequest).start();
} }

View file

@ -412,8 +412,8 @@ public class CronetDataSource extends UrlRequest.Callback implements HttpDataSou
// Internal methods. // Internal methods.
private UrlRequest buildRequest(DataSpec dataSpec) throws OpenException { private UrlRequest buildRequest(DataSpec dataSpec) throws OpenException {
UrlRequest.Builder requestBuilder = new UrlRequest.Builder(dataSpec.uri.toString(), this, UrlRequest.Builder requestBuilder = cronetEngine.newUrlRequestBuilder(dataSpec.uri.toString(),
executor, cronetEngine); this, executor);
// Set the headers. // Set the headers.
synchronized (requestProperties) { synchronized (requestProperties) {
if (dataSpec.postBody != null && dataSpec.postBody.length != 0 if (dataSpec.postBody != null && dataSpec.postBody.length != 0

View file

@ -65,7 +65,7 @@ public class OkHttpDataSource implements HttpDataSource {
private long bytesRead; 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 userAgent The User-Agent string that should be used.
* @param contentTypePredicate An optional {@link Predicate}. If a content type is rejected by the * @param contentTypePredicate An optional {@link Predicate}. If a content type is rejected by the
* predicate then a InvalidContentTypeException} is thrown from {@link #open(DataSpec)}. * 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 userAgent The User-Agent string that should be used.
* @param contentTypePredicate An optional {@link Predicate}. If a content type is rejected by the * @param contentTypePredicate An optional {@link Predicate}. If a content type is rejected by the
* predicate then a {@link InvalidContentTypeException} is thrown from * predicate then a {@link InvalidContentTypeException} is thrown from

View file

@ -177,8 +177,7 @@ public class CacheDataSourceTest extends InstrumentationTestCase {
builder.setSimulateUnknownLength(simulateUnknownLength); builder.setSimulateUnknownLength(simulateUnknownLength);
builder.appendReadData(TEST_DATA); builder.appendReadData(TEST_DATA);
FakeDataSource upstream = builder.build(); FakeDataSource upstream = builder.build();
return new CacheDataSource(simpleCache, upstream, return new CacheDataSource(simpleCache, upstream, CacheDataSource.FLAG_BLOCK_ON_CACHE,
CacheDataSource.FLAG_BLOCK_ON_CACHE | CacheDataSource.FLAG_CACHE_UNBOUNDED_REQUESTS,
MAX_CACHE_FILE_SIZE); MAX_CACHE_FILE_SIZE);
} }

View file

@ -23,7 +23,7 @@ public interface ExoPlayerLibraryInfo {
/** /**
* The version of the library, expressed as a string. * 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. * 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 * corresponding integer version 1002003 (001-002-003), and "123.45.6" has the corresponding
* integer version 123045006 (123-045-006). * 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} * Whether the library was compiled with {@link com.google.android.exoplayer2.util.Assertions}

View file

@ -44,6 +44,7 @@ import java.util.Collections;
private static final int SUFFIX_SEI_NUT = 40; private static final int SUFFIX_SEI_NUT = 40;
private TrackOutput output; private TrackOutput output;
private SampleReader sampleReader;
private SeiReader seiReader; private SeiReader seiReader;
// State that should not be reset on seek. // State that should not be reset on seek.
@ -56,7 +57,6 @@ import java.util.Collections;
private final NalUnitTargetBuffer pps; private final NalUnitTargetBuffer pps;
private final NalUnitTargetBuffer prefixSei; private final NalUnitTargetBuffer prefixSei;
private final NalUnitTargetBuffer suffixSei; // TODO: Are both needed? private final NalUnitTargetBuffer suffixSei; // TODO: Are both needed?
private final SampleReader sampleReader;
private long totalBytesWritten; private long totalBytesWritten;
// Per packet state that gets reset at the start of each packet. // 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); pps = new NalUnitTargetBuffer(PPS_NUT, 128);
prefixSei = new NalUnitTargetBuffer(PREFIX_SEI_NUT, 128); prefixSei = new NalUnitTargetBuffer(PREFIX_SEI_NUT, 128);
suffixSei = new NalUnitTargetBuffer(SUFFIX_SEI_NUT, 128); suffixSei = new NalUnitTargetBuffer(SUFFIX_SEI_NUT, 128);
sampleReader = new SampleReader(output);
seiWrapper = new ParsableByteArray(); seiWrapper = new ParsableByteArray();
} }
@ -91,6 +90,7 @@ import java.util.Collections;
@Override @Override
public void init(ExtractorOutput extractorOutput, TrackIdGenerator idGenerator) { public void init(ExtractorOutput extractorOutput, TrackIdGenerator idGenerator) {
output = extractorOutput.track(idGenerator.getNextId()); output = extractorOutput.track(idGenerator.getNextId());
sampleReader = new SampleReader(output);
seiReader = new SeiReader(extractorOutput.track(idGenerator.getNextId())); seiReader = new SeiReader(extractorOutput.track(idGenerator.getNextId()));
} }

View file

@ -39,7 +39,6 @@ import com.google.android.exoplayer2.upstream.Loader;
import com.google.android.exoplayer2.upstream.Loader.Loadable; import com.google.android.exoplayer2.upstream.Loader.Loadable;
import com.google.android.exoplayer2.util.Assertions; import com.google.android.exoplayer2.util.Assertions;
import com.google.android.exoplayer2.util.ConditionVariable; import com.google.android.exoplayer2.util.ConditionVariable;
import com.google.android.exoplayer2.util.Util;
import java.io.EOFException; import java.io.EOFException;
import java.io.IOException; import java.io.IOException;
@ -593,8 +592,7 @@ import java.io.IOException;
ExtractorInput input = null; ExtractorInput input = null;
try { try {
long position = positionHolder.position; long position = positionHolder.position;
length = dataSource.open( length = dataSource.open(new DataSpec(uri, position, C.LENGTH_UNSET, null));
new DataSpec(uri, position, C.LENGTH_UNSET, Util.sha1(uri.toString())));
if (length != C.LENGTH_UNSET) { if (length != C.LENGTH_UNSET) {
length += position; length += position;
} }

View file

@ -124,7 +124,7 @@ public final class DashMediaSource implements MediaSource {
this.minLoadableRetryCount = minLoadableRetryCount; this.minLoadableRetryCount = minLoadableRetryCount;
this.livePresentationDelayMs = livePresentationDelayMs; this.livePresentationDelayMs = livePresentationDelayMs;
eventDispatcher = new EventDispatcher(eventHandler, eventListener); eventDispatcher = new EventDispatcher(eventHandler, eventListener);
manifestParser = new DashManifestParser(generateContentId()); manifestParser = new DashManifestParser();
manifestCallback = new ManifestCallback(); manifestCallback = new ManifestCallback();
manifestUriLock = new Object(); manifestUriLock = new Object();
periodsById = new SparseArray<>(); 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 { private static final class PeriodSeekInfo {
public static PeriodSeekInfo createPeriodSeekInfo( public static PeriodSeekInfo createPeriodSeekInfo(

View file

@ -57,7 +57,6 @@ public abstract class Representation {
*/ */
public final long presentationTimeOffsetUs; public final long presentationTimeOffsetUs;
private final String cacheKey;
private final RangedUri initializationUri; private final RangedUri initializationUri;
/** /**
@ -81,7 +80,8 @@ public abstract class Representation {
* @param revisionId Identifies the revision of the content. * @param revisionId Identifies the revision of the content.
* @param format The format of the representation. * @param format The format of the representation.
* @param segmentBase A segment base element for 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. * @return The constructed instance.
*/ */
public static Representation newInstance(String contentId, long revisionId, Format format, public static Representation newInstance(String contentId, long revisionId, Format format,
@ -91,7 +91,7 @@ public abstract class Representation {
(SingleSegmentBase) segmentBase, customCacheKey, C.LENGTH_UNSET); (SingleSegmentBase) segmentBase, customCacheKey, C.LENGTH_UNSET);
} else if (segmentBase instanceof MultiSegmentBase) { } else if (segmentBase instanceof MultiSegmentBase) {
return new MultiSegmentRepresentation(contentId, revisionId, format, return new MultiSegmentRepresentation(contentId, revisionId, format,
(MultiSegmentBase) segmentBase, customCacheKey); (MultiSegmentBase) segmentBase);
} else { } else {
throw new IllegalArgumentException("segmentBase must be of type SingleSegmentBase or " throw new IllegalArgumentException("segmentBase must be of type SingleSegmentBase or "
+ "MultiSegmentBase"); + "MultiSegmentBase");
@ -99,12 +99,10 @@ public abstract class Representation {
} }
private Representation(String contentId, long revisionId, Format format, private Representation(String contentId, long revisionId, Format format,
SegmentBase segmentBase, String customCacheKey) { SegmentBase segmentBase) {
this.contentId = contentId; this.contentId = contentId;
this.revisionId = revisionId; this.revisionId = revisionId;
this.format = format; this.format = format;
this.cacheKey = customCacheKey != null ? customCacheKey
: contentId + "." + format.id + "." + revisionId;
initializationUri = segmentBase.getInitialization(this); initializationUri = segmentBase.getInitialization(this);
presentationTimeOffsetUs = segmentBase.getPresentationTimeOffsetUs(); presentationTimeOffsetUs = segmentBase.getPresentationTimeOffsetUs();
} }
@ -129,12 +127,10 @@ public abstract class Representation {
public abstract DashSegmentIndex getIndex(); public abstract DashSegmentIndex getIndex();
/** /**
* Returns a cache key for the representation, in the format * Returns a cache key for the representation if a custom cache key or content id has been
* {@code contentId + "." + format.id + "." + revisionId}. * provided and there is only single segment.
*/ */
public String getCacheKey() { public abstract String getCacheKey();
return cacheKey;
}
/** /**
* A DASH representation consisting of a single segment. * A DASH representation consisting of a single segment.
@ -151,6 +147,7 @@ public abstract class Representation {
*/ */
public final long contentLength; public final long contentLength;
private final String cacheKey;
private final RangedUri indexUri; private final RangedUri indexUri;
private final SingleSegmentIndex segmentIndex; private final SingleSegmentIndex segmentIndex;
@ -187,9 +184,11 @@ public abstract class Representation {
*/ */
public SingleSegmentRepresentation(String contentId, long revisionId, Format format, public SingleSegmentRepresentation(String contentId, long revisionId, Format format,
SingleSegmentBase segmentBase, String customCacheKey, long contentLength) { SingleSegmentBase segmentBase, String customCacheKey, long contentLength) {
super(contentId, revisionId, format, segmentBase, customCacheKey); super(contentId, revisionId, format, segmentBase);
this.uri = Uri.parse(segmentBase.uri); this.uri = Uri.parse(segmentBase.uri);
this.indexUri = segmentBase.getIndex(); this.indexUri = segmentBase.getIndex();
this.cacheKey = customCacheKey != null ? customCacheKey
: contentId != null ? contentId + "." + format.id + "." + revisionId : null;
this.contentLength = contentLength; this.contentLength = contentLength;
// If we have an index uri then the index is defined externally, and we shouldn't return one // 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. // 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; 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 revisionId Identifies the revision of the content.
* @param format The format of the representation. * @param format The format of the representation.
* @param segmentBase The segment base underlying 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, public MultiSegmentRepresentation(String contentId, long revisionId, Format format,
MultiSegmentBase segmentBase, String customCacheKey) { MultiSegmentBase segmentBase) {
super(contentId, revisionId, format, segmentBase, customCacheKey); super(contentId, revisionId, format, segmentBase);
this.segmentBase = segmentBase; this.segmentBase = segmentBase;
} }
@ -240,6 +243,11 @@ public abstract class Representation {
return this; return this;
} }
@Override
public String getCacheKey() {
return null;
}
// DashSegmentIndex implementation. // DashSegmentIndex implementation.
@Override @Override

View file

@ -440,6 +440,10 @@ import java.util.Locale;
} }
HlsMediaPlaylist oldMediaPlaylist = variantPlaylists[oldVariantIndex]; HlsMediaPlaylist oldMediaPlaylist = variantPlaylists[oldVariantIndex];
HlsMediaPlaylist newMediaPlaylist = variantPlaylists[newVariantIndex]; HlsMediaPlaylist newMediaPlaylist = variantPlaylists[newVariantIndex];
if (previousChunkIndex < oldMediaPlaylist.mediaSequence) {
// We have fallen behind the live window.
return newMediaPlaylist.mediaSequence - 1;
}
double offsetToLiveInstantSecs = 0; double offsetToLiveInstantSecs = 0;
for (int i = previousChunkIndex - oldMediaPlaylist.mediaSequence; for (int i = previousChunkIndex - oldMediaPlaylist.mediaSequence;
i < oldMediaPlaylist.segments.size(); i++) { i < oldMediaPlaylist.segments.size(); i++) {

View file

@ -15,6 +15,7 @@
*/ */
package com.google.android.exoplayer2.ui; package com.google.android.exoplayer2.ui;
import android.annotation.TargetApi;
import android.content.Context; import android.content.Context;
import android.content.res.TypedArray; import android.content.res.TypedArray;
import android.os.SystemClock; import android.os.SystemClock;
@ -75,6 +76,7 @@ public class PlaybackControlView extends FrameLayout {
private ExoPlayer player; private ExoPlayer player;
private VisibilityListener visibilityListener; private VisibilityListener visibilityListener;
private boolean isAttachedToWindow;
private boolean dragging; private boolean dragging;
private int rewindMs; private int rewindMs;
private int fastForwardMs; private int fastForwardMs;
@ -264,7 +266,7 @@ public class PlaybackControlView extends FrameLayout {
removeCallbacks(hideAction); removeCallbacks(hideAction);
if (showTimeoutMs > 0) { if (showTimeoutMs > 0) {
hideAtMs = SystemClock.uptimeMillis() + showTimeoutMs; hideAtMs = SystemClock.uptimeMillis() + showTimeoutMs;
if (isAttachedToWindow()) { if (isAttachedToWindow) {
postDelayed(hideAction, showTimeoutMs); postDelayed(hideAction, showTimeoutMs);
} }
} else { } else {
@ -279,7 +281,7 @@ public class PlaybackControlView extends FrameLayout {
} }
private void updatePlayPauseButton() { private void updatePlayPauseButton() {
if (!isVisible() || !isAttachedToWindow()) { if (!isVisible() || !isAttachedToWindow) {
return; return;
} }
boolean playing = player != null && player.getPlayWhenReady(); boolean playing = player != null && player.getPlayWhenReady();
@ -291,7 +293,7 @@ public class PlaybackControlView extends FrameLayout {
} }
private void updateNavigation() { private void updateNavigation() {
if (!isVisible() || !isAttachedToWindow()) { if (!isVisible() || !isAttachedToWindow) {
return; return;
} }
Timeline currentTimeline = player != null ? player.getCurrentTimeline() : null; Timeline currentTimeline = player != null ? player.getCurrentTimeline() : null;
@ -315,7 +317,7 @@ public class PlaybackControlView extends FrameLayout {
} }
private void updateProgress() { private void updateProgress() {
if (!isVisible() || !isAttachedToWindow()) { if (!isVisible() || !isAttachedToWindow) {
return; return;
} }
long duration = player == null ? 0 : player.getDuration(); long duration = player == null ? 0 : player.getDuration();
@ -350,13 +352,18 @@ public class PlaybackControlView extends FrameLayout {
private void setButtonEnabled(boolean enabled, View view) { private void setButtonEnabled(boolean enabled, View view) {
view.setEnabled(enabled); view.setEnabled(enabled);
if (Util.SDK_INT >= 11) { if (Util.SDK_INT >= 11) {
view.setAlpha(enabled ? 1f : 0.3f); setViewAlphaV11(view, enabled ? 1f : 0.3f);
view.setVisibility(VISIBLE); view.setVisibility(VISIBLE);
} else { } else {
view.setVisibility(enabled ? VISIBLE : INVISIBLE); view.setVisibility(enabled ? VISIBLE : INVISIBLE);
} }
} }
@TargetApi(11)
private void setViewAlphaV11(View view, float alpha) {
view.setAlpha(alpha);
}
private String stringForTime(long timeMs) { private String stringForTime(long timeMs) {
if (timeMs == C.TIME_UNSET) { if (timeMs == C.TIME_UNSET) {
timeMs = 0; timeMs = 0;
@ -426,6 +433,7 @@ public class PlaybackControlView extends FrameLayout {
@Override @Override
public void onAttachedToWindow() { public void onAttachedToWindow() {
super.onAttachedToWindow(); super.onAttachedToWindow();
isAttachedToWindow = true;
if (hideAtMs != C.TIME_UNSET) { if (hideAtMs != C.TIME_UNSET) {
long delayMs = hideAtMs - SystemClock.uptimeMillis(); long delayMs = hideAtMs - SystemClock.uptimeMillis();
if (delayMs <= 0) { if (delayMs <= 0) {
@ -440,6 +448,7 @@ public class PlaybackControlView extends FrameLayout {
@Override @Override
public void onDetachedFromWindow() { public void onDetachedFromWindow() {
super.onDetachedFromWindow(); super.onDetachedFromWindow();
isAttachedToWindow = false;
removeCallbacks(updateProgressAction); removeCallbacks(updateProgressAction);
removeCallbacks(hideAction); removeCallbacks(hideAction);
} }

View file

@ -26,6 +26,7 @@ import com.google.android.exoplayer2.upstream.DataSpec;
import com.google.android.exoplayer2.upstream.FileDataSource; import com.google.android.exoplayer2.upstream.FileDataSource;
import com.google.android.exoplayer2.upstream.TeeDataSource; import com.google.android.exoplayer2.upstream.TeeDataSource;
import com.google.android.exoplayer2.upstream.cache.CacheDataSink.CacheDataSinkException; import com.google.android.exoplayer2.upstream.cache.CacheDataSink.CacheDataSinkException;
import com.google.android.exoplayer2.util.Util;
import java.io.IOException; import java.io.IOException;
import java.io.InterruptedIOException; import java.io.InterruptedIOException;
import java.lang.annotation.Retention; import java.lang.annotation.Retention;
@ -50,8 +51,7 @@ public final class CacheDataSource implements DataSource {
* Flags controlling the cache's behavior. * Flags controlling the cache's behavior.
*/ */
@Retention(RetentionPolicy.SOURCE) @Retention(RetentionPolicy.SOURCE)
@IntDef(flag = true, value = {FLAG_BLOCK_ON_CACHE, FLAG_IGNORE_CACHE_ON_ERROR, @IntDef(flag = true, value = {FLAG_BLOCK_ON_CACHE, FLAG_IGNORE_CACHE_ON_ERROR})
FLAG_CACHE_UNBOUNDED_REQUESTS})
public @interface Flags {} public @interface Flags {}
/** /**
* A flag indicating whether we will block reads if the cache key is locked. If this flag is * 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; 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. * Listener of {@link CacheDataSource} events.
*/ */
@ -98,7 +91,6 @@ public final class CacheDataSource implements DataSource {
private final boolean blockOnCache; private final boolean blockOnCache;
private final boolean ignoreCacheOnError; private final boolean ignoreCacheOnError;
private final boolean bypassUnboundedRequests;
private DataSource currentDataSource; private DataSource currentDataSource;
private boolean currentRequestUnbounded; private boolean currentRequestUnbounded;
@ -127,8 +119,8 @@ public final class CacheDataSource implements DataSource {
* *
* @param cache The cache. * @param cache The cache.
* @param upstream A {@link DataSource} for reading data not in 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} * @param flags A combination of {@link #FLAG_BLOCK_ON_CACHE} and {@link
* and {@link #FLAG_CACHE_UNBOUNDED_REQUESTS} or 0. * #FLAG_IGNORE_CACHE_ON_ERROR} or 0.
* @param maxCacheFileSize The maximum size of a cache file, in bytes. If the cached data size * @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 * 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. * 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 upstream A {@link DataSource} for reading data not in the cache.
* @param cacheReadDataSource A {@link DataSource} for reading data from 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 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} * @param flags A combination of {@link #FLAG_BLOCK_ON_CACHE} and {@link
* and {@link #FLAG_CACHE_UNBOUNDED_REQUESTS} or 0. * #FLAG_IGNORE_CACHE_ON_ERROR} or 0.
* @param eventListener An optional {@link EventListener} to receive events. * @param eventListener An optional {@link EventListener} to receive events.
*/ */
public CacheDataSource(Cache cache, DataSource upstream, DataSource cacheReadDataSource, public CacheDataSource(Cache cache, DataSource upstream, DataSource cacheReadDataSource,
@ -158,7 +150,6 @@ public final class CacheDataSource implements DataSource {
this.cacheReadDataSource = cacheReadDataSource; this.cacheReadDataSource = cacheReadDataSource;
this.blockOnCache = (flags & FLAG_BLOCK_ON_CACHE) != 0; this.blockOnCache = (flags & FLAG_BLOCK_ON_CACHE) != 0;
this.ignoreCacheOnError = (flags & FLAG_IGNORE_CACHE_ON_ERROR) != 0; this.ignoreCacheOnError = (flags & FLAG_IGNORE_CACHE_ON_ERROR) != 0;
this.bypassUnboundedRequests = (flags & FLAG_CACHE_UNBOUNDED_REQUESTS) == 0;
this.upstreamDataSource = upstream; this.upstreamDataSource = upstream;
if (cacheWriteDataSink != null) { if (cacheWriteDataSink != null) {
this.cacheWriteDataSource = new TeeDataSource(upstream, cacheWriteDataSink); this.cacheWriteDataSource = new TeeDataSource(upstream, cacheWriteDataSink);
@ -173,10 +164,9 @@ public final class CacheDataSource implements DataSource {
try { try {
uri = dataSpec.uri; uri = dataSpec.uri;
flags = dataSpec.flags; flags = dataSpec.flags;
key = dataSpec.key; key = dataSpec.key != null ? dataSpec.key : Util.sha1(uri.toString());
readPosition = dataSpec.position; readPosition = dataSpec.position;
currentRequestIgnoresCache = (ignoreCacheOnError && seenCacheError) currentRequestIgnoresCache = ignoreCacheOnError && seenCacheError;
|| (bypassUnboundedRequests && dataSpec.length == C.LENGTH_UNSET);
if (dataSpec.length != C.LENGTH_UNSET || currentRequestIgnoresCache) { if (dataSpec.length != C.LENGTH_UNSET || currentRequestIgnoresCache) {
bytesRemaining = dataSpec.length; bytesRemaining = dataSpec.length;
} else { } else {

View file

@ -17,8 +17,8 @@
<manifest xmlns:android="http://schemas.android.com/apk/res/android" <manifest xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools" xmlns:tools="http://schemas.android.com/tools"
package="com.google.android.exoplayer2.playbacktests" package="com.google.android.exoplayer2.playbacktests"
android:versionCode="2003" android:versionCode="2004"
android:versionName="2.0.3"> android:versionName="2.0.4">
<uses-permission android:name="android.permission.INTERNET"/> <uses-permission android:name="android.permission.INTERNET"/>
<uses-permission android:name="android.permission.WAKE_LOCK"/> <uses-permission android:name="android.permission.WAKE_LOCK"/>