use of com.google.android.exoplayer2.upstream.cache.CacheDataSource in project ExoPlayer by google.
the class CacheDataSourceTest method switchToCacheSourceWithNonBlockingCacheDataSource.
@Test
public void switchToCacheSourceWithNonBlockingCacheDataSource() throws Exception {
// Create a fake data source with a 1 MB default data.
FakeDataSource upstream = new FakeDataSource();
FakeData fakeData = upstream.getDataSet().newDefaultData().appendReadData(1024 * 1024 - 1);
// Insert an action just before the end of the data to fail the test if reading from upstream
// reaches end of the data.
fakeData.appendReadAction(() -> fail("Read from upstream shouldn't reach to the end of the data.")).appendReadData(1);
// Lock the content on the cache.
CacheSpan cacheSpan = cache.startReadWriteNonBlocking(defaultCacheKey, 0, C.LENGTH_UNSET);
assertThat(cacheSpan).isNotNull();
assertThat(cacheSpan.isHoleSpan()).isTrue();
// Create non blocking CacheDataSource.
CacheDataSource cacheDataSource = new CacheDataSource(cache, upstream, 0);
// Open source and read some data from upstream without writing to cache as the data is locked.
cacheDataSource.open(unboundedDataSpec);
byte[] buffer = new byte[1024];
cacheDataSource.read(buffer, 0, buffer.length);
// Unlock the span.
cache.releaseHoleSpan(cacheSpan);
assertCacheEmpty(cache);
// Cache the data. Although we use another FakeDataSource instance, it shouldn't matter.
FakeDataSource upstream2 = new FakeDataSource(new FakeDataSource().getDataSet().newDefaultData().appendReadData(1024 * 1024).endData());
CacheWriter cacheWriter = new CacheWriter(new CacheDataSource(cache, upstream2), unboundedDataSpec, /* temporaryBuffer= */
null, /* progressListener= */
null);
cacheWriter.cache();
// Read the rest of the data.
DataSourceUtil.readToEnd(cacheDataSource);
cacheDataSource.close();
}
use of com.google.android.exoplayer2.upstream.cache.CacheDataSource in project ExoPlayer by google.
the class CacheDataSourceTest method deleteCachedWhileReadingFromUpstreamWithBlockingCacheDataSourceDoesNotBlock.
@Test
public void deleteCachedWhileReadingFromUpstreamWithBlockingCacheDataSourceDoesNotBlock() throws Exception {
// Create a fake data source with a 1 KB default data.
FakeDataSource upstream = new FakeDataSource();
int dataLength = 1024;
upstream.getDataSet().newDefaultData().appendReadData(dataLength).endData();
// Cache the latter half of the data.
int halfDataLength = 512;
DataSpec dataSpec = buildDataSpec(/* position= */
0, halfDataLength);
CacheWriter cacheWriter = new CacheWriter(new CacheDataSource(cache, upstream), dataSpec, /* temporaryBuffer= */
null, /* progressListener= */
null);
cacheWriter.cache();
// Create blocking CacheDataSource.
CacheDataSource cacheDataSource = new CacheDataSource(cache, upstream, CacheDataSource.FLAG_BLOCK_ON_CACHE);
cacheDataSource.open(unboundedDataSpec);
// Read the first half from upstream as it hasn't cached yet.
DataSourceUtil.readExactly(cacheDataSource, halfDataLength);
// Delete the cached latter half.
NavigableSet<CacheSpan> cachedSpans = cache.getCachedSpans(defaultCacheKey);
for (CacheSpan cachedSpan : cachedSpans) {
if (cachedSpan.position >= halfDataLength) {
cache.removeSpan(cachedSpan);
}
}
// Read the rest of the data.
DataSourceUtil.readToEnd(cacheDataSource);
cacheDataSource.close();
}
use of com.google.android.exoplayer2.upstream.cache.CacheDataSource in project ExoPlayer by google.
the class CacheDataSourceTest method unknownLengthContentReadInOneConnectionAndLengthIsResolved.
@Test
public void unknownLengthContentReadInOneConnectionAndLengthIsResolved() throws Exception {
FakeDataSource upstream = new FakeDataSource();
upstream.getDataSet().newData(testDataUri).appendReadData(TEST_DATA).setSimulateUnknownLength(true);
CacheDataSource cacheDataSource = new CacheDataSource(cache, upstream, 0);
cacheDataSource.open(unboundedDataSpec);
DataSourceUtil.readToEnd(cacheDataSource);
cacheDataSource.close();
assertThat(upstream.getAndClearOpenedDataSpecs()).hasLength(1);
assertThat(ContentMetadata.getContentLength(cache.getContentMetadata(defaultCacheKey))).isEqualTo(TEST_DATA.length);
}
use of com.google.android.exoplayer2.upstream.cache.CacheDataSource in project ExoPlayer by google.
the class CacheAsserts method assertDataCached.
/**
* Asserts that the cache contains the given data for {@code dataSpec}.
*
* @throws IOException If an error occurred reading from the Cache.
*/
public static void assertDataCached(Cache cache, DataSpec dataSpec, byte[] expected) throws IOException {
DataSource dataSource = new CacheDataSource(cache, DummyDataSource.INSTANCE, 0);
byte[] bytes;
try {
dataSource.open(dataSpec);
bytes = DataSourceUtil.readToEnd(dataSource);
} catch (IOException e) {
throw new IOException("Opening/reading cache failed: " + dataSpec, e);
} finally {
dataSource.close();
}
assertWithMessage("Cached data doesn't match expected for '" + dataSpec.uri + "',").that(bytes).isEqualTo(expected);
}
use of com.google.android.exoplayer2.upstream.cache.CacheDataSource in project ExoPlayer by google.
the class SegmentDownloader method download.
@Override
public final void download(@Nullable ProgressListener progressListener) throws IOException, InterruptedException {
ArrayDeque<Segment> pendingSegments = new ArrayDeque<>();
ArrayDeque<SegmentDownloadRunnable> recycledRunnables = new ArrayDeque<>();
if (priorityTaskManager != null) {
priorityTaskManager.add(C.PRIORITY_DOWNLOAD);
}
try {
CacheDataSource dataSource = cacheDataSourceFactory.createDataSourceForDownloading();
// Get the manifest and all of the segments.
M manifest = getManifest(dataSource, manifestDataSpec, /* removing= */
false);
if (!streamKeys.isEmpty()) {
manifest = manifest.copy(streamKeys);
}
List<Segment> segments = getSegments(dataSource, manifest, /* removing= */
false);
// Sort the segments so that we download media in the right order from the start of the
// content, and merge segments where possible to minimize the number of server round trips.
Collections.sort(segments);
mergeSegments(segments, cacheKeyFactory);
// Scan the segments, removing any that are fully downloaded.
int totalSegments = segments.size();
int segmentsDownloaded = 0;
long contentLength = 0;
long bytesDownloaded = 0;
for (int i = segments.size() - 1; i >= 0; i--) {
DataSpec dataSpec = segments.get(i).dataSpec;
String cacheKey = cacheKeyFactory.buildCacheKey(dataSpec);
long segmentLength = dataSpec.length;
if (segmentLength == C.LENGTH_UNSET) {
long resourceLength = ContentMetadata.getContentLength(cache.getContentMetadata(cacheKey));
if (resourceLength != C.LENGTH_UNSET) {
segmentLength = resourceLength - dataSpec.position;
}
}
long segmentBytesDownloaded = cache.getCachedBytes(cacheKey, dataSpec.position, segmentLength);
bytesDownloaded += segmentBytesDownloaded;
if (segmentLength != C.LENGTH_UNSET) {
if (segmentLength == segmentBytesDownloaded) {
// The segment is fully downloaded.
segmentsDownloaded++;
segments.remove(i);
}
if (contentLength != C.LENGTH_UNSET) {
contentLength += segmentLength;
}
} else {
contentLength = C.LENGTH_UNSET;
}
}
// Download the segments.
@Nullable ProgressNotifier progressNotifier = progressListener != null ? new ProgressNotifier(progressListener, contentLength, totalSegments, bytesDownloaded, segmentsDownloaded) : null;
pendingSegments.addAll(segments);
while (!isCanceled && !pendingSegments.isEmpty()) {
// Block until there aren't any higher priority tasks.
if (priorityTaskManager != null) {
priorityTaskManager.proceed(C.PRIORITY_DOWNLOAD);
}
// Create and execute a runnable to download the next segment.
CacheDataSource segmentDataSource;
byte[] temporaryBuffer;
if (!recycledRunnables.isEmpty()) {
SegmentDownloadRunnable recycledRunnable = recycledRunnables.removeFirst();
segmentDataSource = recycledRunnable.dataSource;
temporaryBuffer = recycledRunnable.temporaryBuffer;
} else {
segmentDataSource = cacheDataSourceFactory.createDataSourceForDownloading();
temporaryBuffer = new byte[BUFFER_SIZE_BYTES];
}
Segment segment = pendingSegments.removeFirst();
SegmentDownloadRunnable downloadRunnable = new SegmentDownloadRunnable(segment, segmentDataSource, progressNotifier, temporaryBuffer);
addActiveRunnable(downloadRunnable);
executor.execute(downloadRunnable);
// Clean up runnables that have finished.
for (int j = activeRunnables.size() - 1; j >= 0; j--) {
SegmentDownloadRunnable activeRunnable = (SegmentDownloadRunnable) activeRunnables.get(j);
// it's already finished.
if (pendingSegments.isEmpty() || activeRunnable.isDone()) {
try {
activeRunnable.get();
removeActiveRunnable(j);
recycledRunnables.addLast(activeRunnable);
} catch (ExecutionException e) {
Throwable cause = Assertions.checkNotNull(e.getCause());
if (cause instanceof PriorityTooLowException) {
// We need to schedule this segment again in a future loop iteration.
pendingSegments.addFirst(activeRunnable.segment);
removeActiveRunnable(j);
recycledRunnables.addLast(activeRunnable);
} else if (cause instanceof IOException) {
throw (IOException) cause;
} else {
// The cause must be an uncaught Throwable type.
Util.sneakyThrow(cause);
}
}
}
}
// Don't move on to the next segment until the runnable for this segment has started. This
// drip feeds runnables to the executor, rather than providing them all up front.
downloadRunnable.blockUntilStarted();
}
} finally {
// Cancel them to speed this up.
for (int i = 0; i < activeRunnables.size(); i++) {
activeRunnables.get(i).cancel(/* interruptIfRunning= */
true);
}
// do this for the case where the main download thread was interrupted as part of cancelation.
for (int i = activeRunnables.size() - 1; i >= 0; i--) {
activeRunnables.get(i).blockUntilFinished();
removeActiveRunnable(i);
}
if (priorityTaskManager != null) {
priorityTaskManager.remove(C.PRIORITY_DOWNLOAD);
}
}
}
Aggregations