Commit 6673483a by ojw28 Committed by GitHub

Merge pull request #1971 from google/dev-v2

Update dev-v2-id3
parents 3e3248d7 924a0417
# Release notes # # Release notes #
### r2.0.3 ### ### r2.0.4 ###
This release contains important bug fixes. Users of r2.0.0, r2.0.1 and r2.0.2 This release contains important bug fixes. Users of earlier r2.0.x versions
should proactively update to this version. 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 ###
* 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.
......
...@@ -35,7 +35,7 @@ allprojects { ...@@ -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'
} }
} }
...@@ -16,8 +16,8 @@ ...@@ -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"/>
......
...@@ -22,7 +22,6 @@ import static org.junit.Assert.assertFalse; ...@@ -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; ...@@ -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 { ...@@ -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 { ...@@ -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 { ...@@ -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 { ...@@ -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 { ...@@ -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 { ...@@ -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();
} }
......
...@@ -412,8 +412,8 @@ public class CronetDataSource extends UrlRequest.Callback implements HttpDataSou ...@@ -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
......
...@@ -65,7 +65,7 @@ public class OkHttpDataSource implements HttpDataSource { ...@@ -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 { ...@@ -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
......
...@@ -177,8 +177,7 @@ public class CacheDataSourceTest extends InstrumentationTestCase { ...@@ -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);
} }
......
...@@ -23,7 +23,7 @@ public interface ExoPlayerLibraryInfo { ...@@ -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 { ...@@ -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}
......
...@@ -44,6 +44,7 @@ import java.util.Collections; ...@@ -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; ...@@ -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; ...@@ -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; ...@@ -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()));
} }
......
...@@ -39,7 +39,6 @@ import com.google.android.exoplayer2.upstream.Loader; ...@@ -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; ...@@ -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;
} }
......
...@@ -124,7 +124,7 @@ public final class DashMediaSource implements MediaSource { ...@@ -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 { ...@@ -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(
......
...@@ -57,7 +57,6 @@ public abstract class Representation { ...@@ -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 { ...@@ -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 { ...@@ -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 { ...@@ -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 { ...@@ -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 { ...@@ -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 { ...@@ -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 { ...@@ -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 { ...@@ -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 { ...@@ -240,6 +243,11 @@ public abstract class Representation {
return this; return this;
} }
@Override
public String getCacheKey() {
return null;
}
// DashSegmentIndex implementation. // DashSegmentIndex implementation.
@Override @Override
......
...@@ -440,6 +440,10 @@ import java.util.Locale; ...@@ -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++) {
......
...@@ -15,6 +15,7 @@ ...@@ -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 { ...@@ -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 { ...@@ -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 { ...@@ -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 { ...@@ -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 { ...@@ -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 { ...@@ -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 { ...@@ -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 { ...@@ -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);
} }
......
...@@ -26,6 +26,7 @@ import com.google.android.exoplayer2.upstream.DataSpec; ...@@ -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 { ...@@ -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
...@@ -67,13 +67,6 @@ public final class CacheDataSource implements DataSource { ...@@ -67,13 +67,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.
*/ */
public interface EventListener { public interface EventListener {
...@@ -98,7 +91,6 @@ public final class CacheDataSource implements DataSource { ...@@ -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 { ...@@ -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 { ...@@ -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 { ...@@ -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 { ...@@ -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 {
......
...@@ -17,8 +17,8 @@ ...@@ -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"/>
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or sign in to comment