Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add option that caches video thumbnails to disk #2197

Open
wants to merge 2 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@
import com.facebook.imagepipeline.decoder.ImageDecoder;
import com.facebook.imagepipeline.decoder.ProgressiveJpegConfig;
import com.facebook.imagepipeline.image.CloseableImage;
import com.facebook.imagepipeline.memory.BitmapPool;
import com.facebook.imageutils.BitmapUtil;

/**
Expand Down Expand Up @@ -251,6 +252,7 @@ public interface ProducerFactoryMethod {

ProducerFactory createProducerFactory(
Context context,
BitmapPool bitmapPool,
ByteArrayPool byteArrayPool,
ImageDecoder imageDecoder,
ProgressiveJpegConfig progressiveJpegConfig,
Expand All @@ -276,6 +278,7 @@ public static class DefaultProducerFactoryMethod implements ProducerFactoryMetho
@Override
public ProducerFactory createProducerFactory(
Context context,
BitmapPool bitmapPool,
ByteArrayPool byteArrayPool,
ImageDecoder imageDecoder,
ProgressiveJpegConfig progressiveJpegConfig,
Expand All @@ -296,6 +299,7 @@ public ProducerFactory createProducerFactory(
int maxBitmapSize) {
return new ProducerFactory(
context,
bitmapPool,
byteArrayPool,
imageDecoder,
progressiveJpegConfig,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -349,6 +349,7 @@ private ProducerFactory getProducerFactory() {
.getProducerFactoryMethod()
.createProducerFactory(
mConfig.getContext(),
mConfig.getPoolFactory().getBitmapPool(),
mConfig.getPoolFactory().getSmallByteArrayPool(),
getImageDecoder(),
mConfig.getProgressiveJpegConfig(),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@
import com.facebook.imagepipeline.decoder.ProgressiveJpegConfig;
import com.facebook.imagepipeline.image.CloseableImage;
import com.facebook.imagepipeline.image.EncodedImage;
import com.facebook.imagepipeline.memory.BitmapPool;
import com.facebook.imagepipeline.producers.AddImageTransformMetaDataProducer;
import com.facebook.imagepipeline.producers.BitmapMemoryCacheGetProducer;
import com.facebook.imagepipeline.producers.BitmapMemoryCacheKeyMultiplexProducer;
Expand All @@ -43,6 +44,7 @@
import com.facebook.imagepipeline.producers.LocalFileFetchProducer;
import com.facebook.imagepipeline.producers.LocalResourceFetchProducer;
import com.facebook.imagepipeline.producers.LocalVideoThumbnailProducer;
import com.facebook.imagepipeline.producers.LocalVideoThumbnailProducer2;
import com.facebook.imagepipeline.producers.NetworkFetchProducer;
import com.facebook.imagepipeline.producers.NetworkFetcher;
import com.facebook.imagepipeline.producers.NullProducer;
Expand Down Expand Up @@ -71,6 +73,7 @@ public class ProducerFactory {
private AssetManager mAssetManager;

// Decode dependencies
private final BitmapPool mBitmapPool;
private final ByteArrayPool mByteArrayPool;
private final ImageDecoder mImageDecoder;
private final ProgressiveJpegConfig mProgressiveJpegConfig;
Expand Down Expand Up @@ -101,6 +104,7 @@ public class ProducerFactory {

public ProducerFactory(
Context context,
BitmapPool bitmapPool,
ByteArrayPool byteArrayPool,
ImageDecoder imageDecoder,
ProgressiveJpegConfig progressiveJpegConfig,
Expand All @@ -123,6 +127,7 @@ public ProducerFactory(
mResources = context.getApplicationContext().getResources();
mAssetManager = context.getApplicationContext().getAssets();

mBitmapPool = bitmapPool;
mByteArrayPool = byteArrayPool;
mImageDecoder = imageDecoder;
mProgressiveJpegConfig = progressiveJpegConfig;
Expand Down Expand Up @@ -294,6 +299,14 @@ public LocalVideoThumbnailProducer newLocalVideoThumbnailProducer() {
mContentResolver);
}

public LocalVideoThumbnailProducer2 newLocalVideoThumbnailProducer2() {
return new LocalVideoThumbnailProducer2(
mPooledByteBufferFactory,
mBitmapPool,
mExecutorSupplier.forLocalStorageRead(),
mContentResolver);
}

public NetworkFetchProducer newNetworkFetchProducer(NetworkFetcher networkFetcher) {
return new NetworkFetchProducer(
mPooledByteBufferFactory,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,8 @@ public class ProducerSequenceFactory {
private Producer<EncodedImage> mCommonNetworkFetchToEncodedMemorySequence;
@VisibleForTesting Producer<CloseableReference<CloseableImage>> mLocalImageFileFetchSequence;
@VisibleForTesting Producer<CloseableReference<CloseableImage>> mLocalVideoFileFetchSequence;
@VisibleForTesting Producer<CloseableReference<CloseableImage>> mLocalVideoFileFetchSequence2;
@VisibleForTesting Producer<EncodedImage> mLocalVideoFileFetchToEncodedMemorySequence;
@VisibleForTesting Producer<CloseableReference<CloseableImage>> mLocalContentUriFetchSequence;
@VisibleForTesting Producer<CloseableReference<CloseableImage>> mLocalResourceFetchSequence;
@VisibleForTesting Producer<CloseableReference<CloseableImage>> mLocalAssetFetchSequence;
Expand Down Expand Up @@ -279,12 +281,12 @@ private Producer<CloseableReference<CloseableImage>> getBasicDecodedImageSequenc
case SOURCE_TYPE_NETWORK:
return getNetworkFetchSequence();
case SOURCE_TYPE_LOCAL_VIDEO_FILE:
return getLocalVideoFileFetchSequence();
return getLocalVideoFileFetchSequence(imageRequest);
case SOURCE_TYPE_LOCAL_IMAGE_FILE:
return getLocalImageFileFetchSequence();
case SOURCE_TYPE_LOCAL_CONTENT:
if (MediaUtils.isVideo(mContentResolver.getType(uri))) {
return getLocalVideoFileFetchSequence();
return getLocalVideoFileFetchSequence(imageRequest);
}
return getLocalContentUriFetchSequence();
case SOURCE_TYPE_LOCAL_ASSET:
Expand Down Expand Up @@ -441,6 +443,14 @@ private synchronized Producer<EncodedImage> getBackgroundLocalFileFetchToEncodeM
return mLocalImageFileFetchSequence;
}


private Producer<CloseableReference<CloseableImage>>
getLocalVideoFileFetchSequence(ImageRequest imageRequest) {
imageRequest.setIsLocalVideoUri(true);
return imageRequest.isVideoThumbnailDiskCacheEnabled() ?
getLocalVideoFileFetchSequence2() : getLocalVideoFileFetchSequence();
}

/**
* Bitmap cache get -> thread hand off -> multiplex -> bitmap cache ->
* local video thumbnail
Expand All @@ -456,6 +466,38 @@ private synchronized Producer<EncodedImage> getBackgroundLocalFileFetchToEncodeM
return mLocalVideoFileFetchSequence;
}

/**
* Bitmap cache get -> thread hand off -> multiplex -> bitmap cache ->
* decode -> multiplex -> encoded cache -> disk cache -> (webp transcode) -> local video thumbnail
*/
private synchronized Producer<CloseableReference<CloseableImage>>
getLocalVideoFileFetchSequence2() {
if (mLocalVideoFileFetchSequence2 == null) {
mLocalVideoFileFetchSequence2 =
newBitmapCacheGetToDecodeSequence(getLocalVideoFileFetchToEncodedMemorySequence());
}

return mLocalVideoFileFetchSequence2;
}

/**
* multiplex -> encoded cache -> disk cache -> (webp transcode) -> local video thumbnail.
*/
private synchronized Producer<EncodedImage> getLocalVideoFileFetchToEncodedMemorySequence() {
FrescoSystrace.beginSection("ProducerSequenceFactory#getLocalVideoFileFetchToEncodedMemorySequence");
if (mLocalVideoFileFetchToEncodedMemorySequence == null) {
FrescoSystrace.beginSection("ProducerSequenceFactory#getLocalVideoFileFetchToEncodedMemorySequence:init");
Producer<EncodedImage> inputProducer =
newEncodedCacheMultiplexToTranscodeSequence(
mProducerFactory.newLocalVideoThumbnailProducer2());
mLocalVideoFileFetchToEncodedMemorySequence =
ProducerFactory.newAddImageTransformMetaDataProducer(inputProducer);
FrescoSystrace.endSection();
}
FrescoSystrace.endSection();
return mLocalVideoFileFetchToEncodedMemorySequence;
}

/**
* bitmap cache get ->
* background thread hand-off -> multiplex -> bitmap cache -> decode ->
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,190 @@
/*
* Copyright (c) 2015-present, Facebook, Inc.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/

package com.facebook.imagepipeline.producers;

import android.content.ContentResolver;
import android.database.Cursor;
import android.graphics.Bitmap;
import android.media.ThumbnailUtils;
import android.net.Uri;
import android.os.Build;
import android.provider.DocumentsContract;
import android.provider.MediaStore;
import android.support.annotation.Nullable;
import com.facebook.common.internal.ImmutableMap;
import com.facebook.common.internal.VisibleForTesting;
import com.facebook.common.memory.PooledByteBuffer;
import com.facebook.common.memory.PooledByteBufferFactory;
import com.facebook.common.memory.PooledByteBufferOutputStream;
import com.facebook.common.references.CloseableReference;
import com.facebook.common.util.UriUtil;
import com.facebook.imagepipeline.image.EncodedImage;
import com.facebook.imagepipeline.memory.BitmapPool;
import com.facebook.imagepipeline.request.ImageRequest;
import java.util.Map;
import java.util.concurrent.Executor;

/**
* A producer that creates video thumbnails.
*
* <p> Those thumbnails will be transformed into EncodedImage and put into disk cache if need
*
*/
public class LocalVideoThumbnailProducer2 implements
Producer<EncodedImage> {

public static final String PRODUCER_NAME = "VideoThumbnailProducer";
@VisibleForTesting static final String CREATED_THUMBNAIL = "createdThumbnail";

private final Executor mExecutor;
private final ContentResolver mContentResolver;
private final PooledByteBufferFactory mPooledByteBufferFactory;
private final BitmapPool mBitmapPool;

public LocalVideoThumbnailProducer2(
PooledByteBufferFactory pooledByteBufferFactory,
BitmapPool bitmapPool,
Executor executor,
ContentResolver contentResolver) {
mPooledByteBufferFactory = pooledByteBufferFactory;
mBitmapPool = bitmapPool;
mExecutor = executor;
mContentResolver = contentResolver;
}

@Override
public void produceResults(
final Consumer<EncodedImage> consumer,
final ProducerContext producerContext) {

final ProducerListener listener = producerContext.getListener();
final String requestId = producerContext.getId();
final ImageRequest imageRequest = producerContext.getImageRequest();
final StatefulProducerRunnable cancellableProducerRunnable =
new StatefulProducerRunnable<EncodedImage>(
consumer,
listener,
PRODUCER_NAME,
requestId) {
@Override
protected void onSuccess(EncodedImage result) {
super.onSuccess(result);
listener.onUltimateProducerReached(requestId, PRODUCER_NAME, result != null);
}

@Override
protected void onFailure(Exception e) {
super.onFailure(e);
listener.onUltimateProducerReached(requestId, PRODUCER_NAME, false);
}

@Override
protected EncodedImage getResult() throws Exception {
EncodedImage encodedImage = getEncodedImage(imageRequest);
if (encodedImage == null) {
listener.onUltimateProducerReached(requestId, PRODUCER_NAME, false);
return null;
}
encodedImage.parseMetaData();
listener.onUltimateProducerReached(requestId, PRODUCER_NAME, true);
return encodedImage;
}

@Override
protected Map<String, String> getExtraMapOnSuccess(
final EncodedImage result) {
return ImmutableMap.of(CREATED_THUMBNAIL, String.valueOf(result != null));
}

@Override
protected void disposeResult(EncodedImage result) {
EncodedImage.closeSafely(result);
}
};
producerContext.addCallbacks(
new BaseProducerContextCallbacks() {
@Override
public void onCancellationRequested() {
cancellableProducerRunnable.cancel();
}
});
mExecutor.execute(cancellableProducerRunnable);
}


protected EncodedImage getEncodedImage(ImageRequest imageRequest) {
String path = getLocalFilePath(imageRequest);
if (path == null) {
return null;
}
Bitmap thumbnailBitmap = ThumbnailUtils.createVideoThumbnail(
path,
calculateKind(imageRequest));
if (thumbnailBitmap == null) {
return null;
}

PooledByteBufferOutputStream pooledOutputStream = mPooledByteBufferFactory.newOutputStream();
try {
thumbnailBitmap.compress(thumbnailBitmap.hasAlpha() ? Bitmap.CompressFormat.PNG : Bitmap.CompressFormat.JPEG,
100, pooledOutputStream);
return getByteBufferBackedEncodedImage(pooledOutputStream);
} finally {
mBitmapPool.release(thumbnailBitmap);
pooledOutputStream.close();
}
}

private EncodedImage getByteBufferBackedEncodedImage(
PooledByteBufferOutputStream pooledOutputStream) {
CloseableReference<PooledByteBuffer> result = null;
try {
result = CloseableReference.of(pooledOutputStream.toByteBuffer());
return new EncodedImage(result);
} finally {
CloseableReference.closeSafely(result);
}
}

private static int calculateKind(ImageRequest imageRequest) {
if (imageRequest.getPreferredWidth() > 96 || imageRequest.getPreferredHeight() > 96) {
return MediaStore.Images.Thumbnails.MINI_KIND;
}
return MediaStore.Images.Thumbnails.MICRO_KIND;
}

@Nullable private String getLocalFilePath(ImageRequest imageRequest) {
Uri uri = imageRequest.getSourceUri();
if (UriUtil.isLocalFileUri(uri)) {
return imageRequest.getSourceFile().getPath();
} else if (UriUtil.isLocalContentUri(uri)) {
String selection = null;
String[] selectionArgs = null;
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT
&& "com.android.providers.media.documents".equals(uri.getAuthority())) {
String documentId = DocumentsContract.getDocumentId(uri);
uri = MediaStore.Video.Media.EXTERNAL_CONTENT_URI;
selection = MediaStore.Video.Media._ID + "=?";
selectionArgs = new String[] {documentId.split(":")[1]};
}
Cursor cursor =
mContentResolver.query(
uri, new String[] {MediaStore.Video.Media.DATA}, selection, selectionArgs, null);
try {
if (cursor != null && cursor.moveToFirst()) {
return cursor.getString(cursor.getColumnIndexOrThrow(MediaStore.Video.Media.DATA));
}
} finally {
if (cursor != null) {
cursor.close();
}
}
}
return null;
}
}
Loading