mirror of
https://github.com/facebook/react-native.git
synced 2025-11-01 09:14:26 +00:00
Move CameraRoll Android files to FB Internal
Summary: Changelog: [Lean Core][Android] Removed CameraRoll from RN Android Reviewed By: RSNara Differential Revision: D22410115 fbshipit-source-id: 5cdb5b87a7bf5b4a8a4c2953c14ba3751b125d07
This commit is contained in:
committed by
Facebook GitHub Bot
parent
f28c7505fa
commit
1930039261
-37
@@ -1,37 +0,0 @@
|
||||
/**
|
||||
* Copyright (c) Facebook, Inc. and its affiliates.
|
||||
*
|
||||
* <p>This source code is licensed under the MIT license found in the LICENSE file in the root
|
||||
* directory of this source tree.
|
||||
*
|
||||
* <p>Generated by an internal genrule from Flow types.
|
||||
*
|
||||
* @generated
|
||||
* @nolint
|
||||
*/
|
||||
|
||||
package com.facebook.fbreact.specs;
|
||||
|
||||
import com.facebook.react.bridge.Promise;
|
||||
import com.facebook.react.bridge.ReactApplicationContext;
|
||||
import com.facebook.react.bridge.ReactContextBaseJavaModule;
|
||||
import com.facebook.react.bridge.ReactMethod;
|
||||
import com.facebook.react.bridge.ReactModuleWithSpec;
|
||||
import com.facebook.react.bridge.ReadableArray;
|
||||
import com.facebook.react.bridge.ReadableMap;
|
||||
import com.facebook.react.turbomodule.core.interfaces.TurboModule;
|
||||
|
||||
public abstract class NativeCameraRollManagerSpec extends ReactContextBaseJavaModule implements ReactModuleWithSpec, TurboModule {
|
||||
public NativeCameraRollManagerSpec(ReactApplicationContext reactContext) {
|
||||
super(reactContext);
|
||||
}
|
||||
|
||||
@ReactMethod
|
||||
public abstract void getPhotos(ReadableMap params, Promise promise);
|
||||
|
||||
@ReactMethod
|
||||
public abstract void deletePhotos(ReadableArray assets, Promise promise);
|
||||
|
||||
@ReactMethod
|
||||
public abstract void saveToCameraRoll(String uri, String type, Promise promise);
|
||||
}
|
||||
@@ -1,545 +0,0 @@
|
||||
/*
|
||||
* Copyright (c) Facebook, Inc. and its affiliates.
|
||||
*
|
||||
* 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.react.modules.camera;
|
||||
|
||||
import android.content.ContentResolver;
|
||||
import android.content.Context;
|
||||
import android.content.res.AssetFileDescriptor;
|
||||
import android.database.Cursor;
|
||||
import android.graphics.BitmapFactory;
|
||||
import android.media.MediaMetadataRetriever;
|
||||
import android.media.MediaScannerConnection;
|
||||
import android.net.Uri;
|
||||
import android.os.AsyncTask;
|
||||
import android.os.Build;
|
||||
import android.os.Environment;
|
||||
import android.provider.MediaStore;
|
||||
import android.provider.MediaStore.Images;
|
||||
import android.text.TextUtils;
|
||||
import androidx.annotation.Nullable;
|
||||
import com.facebook.common.logging.FLog;
|
||||
import com.facebook.fbreact.specs.NativeCameraRollManagerSpec;
|
||||
import com.facebook.react.bridge.GuardedAsyncTask;
|
||||
import com.facebook.react.bridge.JSApplicationIllegalArgumentException;
|
||||
import com.facebook.react.bridge.NativeModule;
|
||||
import com.facebook.react.bridge.Promise;
|
||||
import com.facebook.react.bridge.ReactApplicationContext;
|
||||
import com.facebook.react.bridge.ReactContext;
|
||||
import com.facebook.react.bridge.ReadableArray;
|
||||
import com.facebook.react.bridge.ReadableMap;
|
||||
import com.facebook.react.bridge.WritableArray;
|
||||
import com.facebook.react.bridge.WritableMap;
|
||||
import com.facebook.react.bridge.WritableNativeArray;
|
||||
import com.facebook.react.bridge.WritableNativeMap;
|
||||
import com.facebook.react.common.ReactConstants;
|
||||
import com.facebook.react.module.annotations.ReactModule;
|
||||
import java.io.File;
|
||||
import java.io.FileInputStream;
|
||||
import java.io.FileOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.net.URL;
|
||||
import java.nio.ByteBuffer;
|
||||
import java.nio.channels.Channels;
|
||||
import java.nio.channels.FileChannel;
|
||||
import java.nio.channels.ReadableByteChannel;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
|
||||
// TODO #6015104: rename to something less iOSish
|
||||
/**
|
||||
* {@link NativeModule} that allows JS to interact with the photos and videos on the device (i.e.
|
||||
* {@link MediaStore.Images}).
|
||||
*/
|
||||
@ReactModule(name = CameraRollManager.NAME)
|
||||
public class CameraRollManager extends NativeCameraRollManagerSpec {
|
||||
|
||||
public static final String NAME = "CameraRollManager";
|
||||
|
||||
private static final String ERROR_UNABLE_TO_LOAD = "E_UNABLE_TO_LOAD";
|
||||
private static final String ERROR_UNABLE_TO_LOAD_PERMISSION = "E_UNABLE_TO_LOAD_PERMISSION";
|
||||
private static final String ERROR_UNABLE_TO_SAVE = "E_UNABLE_TO_SAVE";
|
||||
private static final String ERROR_UNABLE_TO_FILTER = "E_UNABLE_TO_FILTER";
|
||||
|
||||
private static final String ASSET_TYPE_PHOTOS = "Photos";
|
||||
private static final String ASSET_TYPE_VIDEOS = "Videos";
|
||||
private static final String ASSET_TYPE_ALL = "All";
|
||||
|
||||
private static final String SELECTION_BUCKET = Images.Media.BUCKET_DISPLAY_NAME + " = ?";
|
||||
private static final String SELECTION_DATE_TAKEN = Images.Media.DATE_TAKEN + " < ?";
|
||||
private static final String SELECTION_MEDIA_SIZE = Images.Media.SIZE + " < ?";
|
||||
|
||||
private static final int IMAGES_MEDIA_LATITUDE_LONGITUDE_DEPRECATED_API_LEVEL = 29;
|
||||
private static final String[] PROJECTION_LIST;
|
||||
|
||||
static {
|
||||
ArrayList<String> projection_list =
|
||||
new ArrayList<>(
|
||||
Arrays.asList(
|
||||
Images.Media._ID,
|
||||
Images.Media.MIME_TYPE,
|
||||
Images.Media.BUCKET_DISPLAY_NAME,
|
||||
Images.Media.DATE_TAKEN,
|
||||
MediaStore.MediaColumns.WIDTH,
|
||||
MediaStore.MediaColumns.HEIGHT,
|
||||
MediaStore.MediaColumns.DATA));
|
||||
if (Build.VERSION.SDK_INT < IMAGES_MEDIA_LATITUDE_LONGITUDE_DEPRECATED_API_LEVEL) {
|
||||
projection_list.add(Images.Media.LATITUDE);
|
||||
projection_list.add(Images.Media.LONGITUDE);
|
||||
PROJECTION_LIST = projection_list.toArray(new String[0]);
|
||||
} else {
|
||||
PROJECTION_LIST = projection_list.toArray(new String[0]);
|
||||
}
|
||||
}
|
||||
|
||||
public CameraRollManager(ReactApplicationContext reactContext) {
|
||||
super(reactContext);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getName() {
|
||||
return NAME;
|
||||
}
|
||||
|
||||
/**
|
||||
* Save an image to the gallery (i.e. {@link MediaStore.Images}). This copies the original file
|
||||
* from wherever it may be to the external storage pictures directory, so that it can be scanned
|
||||
* by the MediaScanner.
|
||||
*
|
||||
* @param uri the file://, http:// or https:// URI of the image to save
|
||||
* @param promise to be resolved or rejected
|
||||
*/
|
||||
@Override
|
||||
public void saveToCameraRoll(String uri, String type, Promise promise) {
|
||||
new SaveToCameraRoll(getReactApplicationContext(), Uri.parse(uri), promise)
|
||||
.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR);
|
||||
}
|
||||
|
||||
private static class SaveToCameraRoll extends GuardedAsyncTask<Void, Void> {
|
||||
|
||||
private static final int SAVE_BUFFER_SIZE = 1048576; // 1MB
|
||||
private final Context mContext;
|
||||
private final Uri mUri;
|
||||
private final Promise mPromise;
|
||||
|
||||
public SaveToCameraRoll(ReactContext context, Uri uri, Promise promise) {
|
||||
super(context);
|
||||
mContext = context;
|
||||
mUri = uri;
|
||||
mPromise = promise;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void doInBackgroundGuarded(Void... params) {
|
||||
ReadableByteChannel input = null;
|
||||
FileChannel output = null;
|
||||
File source = new File(mUri.getPath());
|
||||
try {
|
||||
String scheme = mUri.getScheme();
|
||||
if (scheme.equals("http") || scheme.equals("https")) {
|
||||
input = Channels.newChannel(new URL(mUri.toString()).openStream());
|
||||
} else {
|
||||
input = new FileInputStream(source).getChannel();
|
||||
}
|
||||
File exportDir = Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DCIM);
|
||||
exportDir.mkdirs();
|
||||
if (!exportDir.isDirectory()) {
|
||||
mPromise.reject(ERROR_UNABLE_TO_LOAD, "External media storage directory not available");
|
||||
return;
|
||||
}
|
||||
File dest = new File(exportDir, source.getName());
|
||||
int n = 0;
|
||||
String fullSourceName = source.getName();
|
||||
String sourceName, sourceExt;
|
||||
if (fullSourceName.indexOf('.') >= 0) {
|
||||
sourceName = fullSourceName.substring(0, fullSourceName.lastIndexOf('.'));
|
||||
sourceExt = fullSourceName.substring(fullSourceName.lastIndexOf('.'));
|
||||
} else {
|
||||
sourceName = fullSourceName;
|
||||
sourceExt = "";
|
||||
}
|
||||
while (!dest.createNewFile()) {
|
||||
dest = new File(exportDir, sourceName + "_" + (n++) + sourceExt);
|
||||
}
|
||||
output = new FileOutputStream(dest).getChannel();
|
||||
// Performs a buffered copy
|
||||
final ByteBuffer buffer = ByteBuffer.allocate(SAVE_BUFFER_SIZE);
|
||||
while (input.read(buffer) > 0) {
|
||||
buffer.flip();
|
||||
output.write(buffer);
|
||||
buffer.compact();
|
||||
}
|
||||
// Drains the buffer
|
||||
buffer.flip();
|
||||
while (buffer.hasRemaining()) {
|
||||
output.write(buffer);
|
||||
}
|
||||
input.close();
|
||||
output.close();
|
||||
|
||||
MediaScannerConnection.scanFile(
|
||||
mContext,
|
||||
new String[] {dest.getAbsolutePath()},
|
||||
null,
|
||||
new MediaScannerConnection.OnScanCompletedListener() {
|
||||
@Override
|
||||
public void onScanCompleted(String path, Uri uri) {
|
||||
if (uri != null) {
|
||||
mPromise.resolve(uri.toString());
|
||||
} else {
|
||||
mPromise.reject(ERROR_UNABLE_TO_SAVE, "Could not add image to gallery");
|
||||
}
|
||||
}
|
||||
});
|
||||
} catch (IOException e) {
|
||||
mPromise.reject(e);
|
||||
} finally {
|
||||
if (input != null && input.isOpen()) {
|
||||
try {
|
||||
input.close();
|
||||
} catch (IOException e) {
|
||||
FLog.e(ReactConstants.TAG, "Could not close input channel", e);
|
||||
}
|
||||
}
|
||||
if (output != null && output.isOpen()) {
|
||||
try {
|
||||
output.close();
|
||||
} catch (IOException e) {
|
||||
FLog.e(ReactConstants.TAG, "Could not close output channel", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get photos from {@link MediaStore.Images}, most recent first.
|
||||
*
|
||||
* @param params a map containing the following keys:
|
||||
* <ul>
|
||||
* <li>first (mandatory): a number representing the number of photos to fetch
|
||||
* <li>after (optional): a cursor that matches page_info[end_cursor] returned by a previous
|
||||
* call to {@link #getPhotos}
|
||||
* <li>groupName (optional): an album name
|
||||
* <li>mimeType (optional): restrict returned images to a specific mimetype (e.g.
|
||||
* image/jpeg)
|
||||
* <li>assetType (optional): chooses between either photos or videos from the camera roll.
|
||||
* Valid values are "Photos" or "Videos". Defaults to photos.
|
||||
* </ul>
|
||||
*
|
||||
* @param promise the Promise to be resolved when the photos are loaded; for a format of the
|
||||
* parameters passed to this callback, see {@code getPhotosReturnChecker} in CameraRoll.js
|
||||
*/
|
||||
@Override
|
||||
public void getPhotos(final ReadableMap params, final Promise promise) {
|
||||
int first = params.getInt("first");
|
||||
String after = params.hasKey("after") ? params.getString("after") : null;
|
||||
String groupName = params.hasKey("groupName") ? params.getString("groupName") : null;
|
||||
String assetType =
|
||||
params.hasKey("assetType") ? params.getString("assetType") : ASSET_TYPE_PHOTOS;
|
||||
Integer maxSize = params.hasKey("maxSize") ? params.getInt("maxSize") : null;
|
||||
ReadableArray mimeTypes = params.hasKey("mimeTypes") ? params.getArray("mimeTypes") : null;
|
||||
if (params.hasKey("groupTypes")) {
|
||||
throw new JSApplicationIllegalArgumentException("groupTypes is not supported on Android");
|
||||
}
|
||||
|
||||
new GetMediaTask(
|
||||
getReactApplicationContext(),
|
||||
first,
|
||||
after,
|
||||
groupName,
|
||||
mimeTypes,
|
||||
assetType,
|
||||
maxSize,
|
||||
promise)
|
||||
.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR);
|
||||
}
|
||||
|
||||
private static class GetMediaTask extends GuardedAsyncTask<Void, Void> {
|
||||
private final Context mContext;
|
||||
private final int mFirst;
|
||||
private final @Nullable String mAfter;
|
||||
private final @Nullable String mGroupName;
|
||||
private final @Nullable ReadableArray mMimeTypes;
|
||||
private final Promise mPromise;
|
||||
private final String mAssetType;
|
||||
private final @Nullable Integer mMaxSize;
|
||||
|
||||
private GetMediaTask(
|
||||
ReactContext context,
|
||||
int first,
|
||||
@Nullable String after,
|
||||
@Nullable String groupName,
|
||||
@Nullable ReadableArray mimeTypes,
|
||||
String assetType,
|
||||
@Nullable Integer maxSize,
|
||||
Promise promise) {
|
||||
super(context);
|
||||
mContext = context;
|
||||
mFirst = first;
|
||||
mAfter = after;
|
||||
mGroupName = groupName;
|
||||
mMimeTypes = mimeTypes;
|
||||
mPromise = promise;
|
||||
mAssetType = assetType;
|
||||
mMaxSize = maxSize;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void doInBackgroundGuarded(Void... params) {
|
||||
StringBuilder selection = new StringBuilder("1");
|
||||
List<String> selectionArgs = new ArrayList<>();
|
||||
if (!TextUtils.isEmpty(mAfter)) {
|
||||
selection.append(" AND " + SELECTION_DATE_TAKEN);
|
||||
selectionArgs.add(mAfter);
|
||||
}
|
||||
if (!TextUtils.isEmpty(mGroupName)) {
|
||||
selection.append(" AND " + SELECTION_BUCKET);
|
||||
selectionArgs.add(mGroupName);
|
||||
}
|
||||
if (mMaxSize != null) {
|
||||
selection.append(" AND " + SELECTION_MEDIA_SIZE);
|
||||
selectionArgs.add(mMaxSize.toString());
|
||||
}
|
||||
|
||||
switch (mAssetType) {
|
||||
case ASSET_TYPE_PHOTOS:
|
||||
selection.append(
|
||||
" AND "
|
||||
+ MediaStore.Files.FileColumns.MEDIA_TYPE
|
||||
+ " = "
|
||||
+ MediaStore.Files.FileColumns.MEDIA_TYPE_IMAGE);
|
||||
break;
|
||||
case ASSET_TYPE_VIDEOS:
|
||||
selection.append(
|
||||
" AND "
|
||||
+ MediaStore.Files.FileColumns.MEDIA_TYPE
|
||||
+ " = "
|
||||
+ MediaStore.Files.FileColumns.MEDIA_TYPE_VIDEO);
|
||||
break;
|
||||
case ASSET_TYPE_ALL:
|
||||
selection.append(
|
||||
" AND "
|
||||
+ MediaStore.Files.FileColumns.MEDIA_TYPE
|
||||
+ " IN ("
|
||||
+ MediaStore.Files.FileColumns.MEDIA_TYPE_VIDEO
|
||||
+ ","
|
||||
+ MediaStore.Files.FileColumns.MEDIA_TYPE_IMAGE
|
||||
+ ")");
|
||||
break;
|
||||
default:
|
||||
mPromise.reject(
|
||||
ERROR_UNABLE_TO_FILTER,
|
||||
"Invalid filter option: '"
|
||||
+ mAssetType
|
||||
+ "'. Expected one of '"
|
||||
+ ASSET_TYPE_PHOTOS
|
||||
+ "', '"
|
||||
+ ASSET_TYPE_VIDEOS
|
||||
+ "' or '"
|
||||
+ ASSET_TYPE_ALL
|
||||
+ "'.");
|
||||
return;
|
||||
}
|
||||
|
||||
if (mMimeTypes != null && mMimeTypes.size() > 0) {
|
||||
selection.append(" AND " + Images.Media.MIME_TYPE + " IN (");
|
||||
for (int i = 0; i < mMimeTypes.size(); i++) {
|
||||
selection.append("?,");
|
||||
selectionArgs.add(mMimeTypes.getString(i));
|
||||
}
|
||||
selection.replace(selection.length() - 1, selection.length(), ")");
|
||||
}
|
||||
WritableMap response = new WritableNativeMap();
|
||||
ContentResolver resolver = mContext.getContentResolver();
|
||||
// using LIMIT in the sortOrder is not explicitly supported by the SDK (which does not support
|
||||
// setting a limit at all), but it works because this specific ContentProvider is backed by
|
||||
// an SQLite DB and forwards parameters to it without doing any parsing / validation.
|
||||
try {
|
||||
Cursor media =
|
||||
resolver.query(
|
||||
MediaStore.Files.getContentUri("external"),
|
||||
PROJECTION_LIST,
|
||||
selection.toString(),
|
||||
selectionArgs.toArray(new String[selectionArgs.size()]),
|
||||
Images.Media.DATE_TAKEN
|
||||
+ " DESC, "
|
||||
+ Images.Media.DATE_MODIFIED
|
||||
+ " DESC LIMIT "
|
||||
+ (mFirst
|
||||
+ 1)); // set LIMIT to first + 1 so that we know how to populate page_info
|
||||
if (media == null) {
|
||||
mPromise.reject(ERROR_UNABLE_TO_LOAD, "Could not get media");
|
||||
} else {
|
||||
try {
|
||||
putEdges(resolver, media, response, mFirst);
|
||||
putPageInfo(media, response, mFirst);
|
||||
} finally {
|
||||
media.close();
|
||||
mPromise.resolve(response);
|
||||
}
|
||||
}
|
||||
} catch (SecurityException e) {
|
||||
mPromise.reject(
|
||||
ERROR_UNABLE_TO_LOAD_PERMISSION,
|
||||
"Could not get media: need READ_EXTERNAL_STORAGE permission",
|
||||
e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static void putPageInfo(Cursor media, WritableMap response, int limit) {
|
||||
WritableMap pageInfo = new WritableNativeMap();
|
||||
pageInfo.putBoolean("has_next_page", limit < media.getCount());
|
||||
if (limit < media.getCount()) {
|
||||
media.moveToPosition(limit - 1);
|
||||
pageInfo.putString(
|
||||
"end_cursor", media.getString(media.getColumnIndex(Images.Media.DATE_TAKEN)));
|
||||
}
|
||||
response.putMap("page_info", pageInfo);
|
||||
}
|
||||
|
||||
private static void putEdges(
|
||||
ContentResolver resolver, Cursor media, WritableMap response, int limit) {
|
||||
WritableArray edges = new WritableNativeArray();
|
||||
media.moveToFirst();
|
||||
int idIndex = media.getColumnIndex(Images.Media._ID);
|
||||
int mimeTypeIndex = media.getColumnIndex(Images.Media.MIME_TYPE);
|
||||
int groupNameIndex = media.getColumnIndex(Images.Media.BUCKET_DISPLAY_NAME);
|
||||
int dateTakenIndex = media.getColumnIndex(Images.Media.DATE_TAKEN);
|
||||
int widthIndex = media.getColumnIndex(MediaStore.MediaColumns.WIDTH);
|
||||
int heightIndex = media.getColumnIndex(MediaStore.MediaColumns.HEIGHT);
|
||||
int longitudeIndex = media.getColumnIndex(Images.Media.LONGITUDE);
|
||||
int latitudeIndex = media.getColumnIndex(Images.Media.LATITUDE);
|
||||
int dataIndex = media.getColumnIndex(MediaStore.MediaColumns.DATA);
|
||||
|
||||
for (int i = 0; i < limit && !media.isAfterLast(); i++) {
|
||||
WritableMap edge = new WritableNativeMap();
|
||||
WritableMap node = new WritableNativeMap();
|
||||
boolean imageInfoSuccess =
|
||||
putImageInfo(
|
||||
resolver, media, node, idIndex, widthIndex, heightIndex, dataIndex, mimeTypeIndex);
|
||||
if (imageInfoSuccess) {
|
||||
putBasicNodeInfo(media, node, mimeTypeIndex, groupNameIndex, dateTakenIndex);
|
||||
if (Build.VERSION.SDK_INT < IMAGES_MEDIA_LATITUDE_LONGITUDE_DEPRECATED_API_LEVEL) {
|
||||
putLocationInfo(media, node, longitudeIndex, latitudeIndex);
|
||||
}
|
||||
|
||||
edge.putMap("node", node);
|
||||
edges.pushMap(edge);
|
||||
} else {
|
||||
// we skipped an image because we couldn't get its details (e.g. width/height), so we
|
||||
// decrement i in order to correctly reach the limit, if the cursor has enough rows
|
||||
i--;
|
||||
}
|
||||
media.moveToNext();
|
||||
}
|
||||
response.putArray("edges", edges);
|
||||
}
|
||||
|
||||
private static void putBasicNodeInfo(
|
||||
Cursor media, WritableMap node, int mimeTypeIndex, int groupNameIndex, int dateTakenIndex) {
|
||||
node.putString("type", media.getString(mimeTypeIndex));
|
||||
node.putString("group_name", media.getString(groupNameIndex));
|
||||
node.putDouble("timestamp", media.getLong(dateTakenIndex) / 1000d);
|
||||
}
|
||||
|
||||
private static boolean putImageInfo(
|
||||
ContentResolver resolver,
|
||||
Cursor media,
|
||||
WritableMap node,
|
||||
int idIndex,
|
||||
int widthIndex,
|
||||
int heightIndex,
|
||||
int dataIndex,
|
||||
int mimeTypeIndex) {
|
||||
WritableMap image = new WritableNativeMap();
|
||||
Uri photoUri = Uri.parse("file://" + media.getString(dataIndex));
|
||||
image.putString("uri", photoUri.toString());
|
||||
float width = media.getInt(widthIndex);
|
||||
float height = media.getInt(heightIndex);
|
||||
|
||||
String mimeType = media.getString(mimeTypeIndex);
|
||||
|
||||
if (mimeType != null && mimeType.startsWith("video")) {
|
||||
try {
|
||||
AssetFileDescriptor photoDescriptor = resolver.openAssetFileDescriptor(photoUri, "r");
|
||||
MediaMetadataRetriever retriever = new MediaMetadataRetriever();
|
||||
retriever.setDataSource(photoDescriptor.getFileDescriptor());
|
||||
|
||||
try {
|
||||
if (width <= 0 || height <= 0) {
|
||||
width =
|
||||
Integer.parseInt(
|
||||
retriever.extractMetadata(MediaMetadataRetriever.METADATA_KEY_VIDEO_WIDTH));
|
||||
height =
|
||||
Integer.parseInt(
|
||||
retriever.extractMetadata(MediaMetadataRetriever.METADATA_KEY_VIDEO_HEIGHT));
|
||||
}
|
||||
int timeInMillisec =
|
||||
Integer.parseInt(
|
||||
retriever.extractMetadata(MediaMetadataRetriever.METADATA_KEY_DURATION));
|
||||
int playableDuration = timeInMillisec / 1000;
|
||||
image.putInt("playableDuration", playableDuration);
|
||||
} catch (NumberFormatException e) {
|
||||
FLog.e(
|
||||
ReactConstants.TAG,
|
||||
"Number format exception occurred while trying to fetch video metadata for "
|
||||
+ photoUri.toString(),
|
||||
e);
|
||||
return false;
|
||||
} finally {
|
||||
retriever.release();
|
||||
photoDescriptor.close();
|
||||
}
|
||||
} catch (Exception e) {
|
||||
FLog.e(ReactConstants.TAG, "Could not get video metadata for " + photoUri.toString(), e);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
if (width <= 0 || height <= 0) {
|
||||
try {
|
||||
AssetFileDescriptor photoDescriptor = resolver.openAssetFileDescriptor(photoUri, "r");
|
||||
BitmapFactory.Options options = new BitmapFactory.Options();
|
||||
// Set inJustDecodeBounds to true so we don't actually load the Bitmap, but only get its
|
||||
// dimensions instead.
|
||||
options.inJustDecodeBounds = true;
|
||||
BitmapFactory.decodeFileDescriptor(photoDescriptor.getFileDescriptor(), null, options);
|
||||
width = options.outWidth;
|
||||
height = options.outHeight;
|
||||
photoDescriptor.close();
|
||||
} catch (IOException e) {
|
||||
FLog.e(ReactConstants.TAG, "Could not get width/height for " + photoUri.toString(), e);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
image.putDouble("width", width);
|
||||
image.putDouble("height", height);
|
||||
node.putMap("image", image);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
private static void putLocationInfo(
|
||||
Cursor media, WritableMap node, int longitudeIndex, int latitudeIndex) {
|
||||
double longitude = media.getDouble(longitudeIndex);
|
||||
double latitude = media.getDouble(latitudeIndex);
|
||||
if (longitude > 0 || latitude > 0) {
|
||||
WritableMap location = new WritableNativeMap();
|
||||
location.putDouble("longitude", longitude);
|
||||
location.putDouble("latitude", latitude);
|
||||
node.putMap("location", location);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void deletePhotos(ReadableArray assets, Promise promise) {
|
||||
// iOS only
|
||||
}
|
||||
}
|
||||
@@ -21,7 +21,6 @@ import com.facebook.react.modules.appearance.AppearanceModule;
|
||||
import com.facebook.react.modules.appstate.AppStateModule;
|
||||
import com.facebook.react.modules.blob.BlobModule;
|
||||
import com.facebook.react.modules.blob.FileReaderModule;
|
||||
import com.facebook.react.modules.camera.CameraRollManager;
|
||||
import com.facebook.react.modules.camera.ImageStoreManager;
|
||||
import com.facebook.react.modules.clipboard.ClipboardModule;
|
||||
import com.facebook.react.modules.datepicker.DatePickerDialogModule;
|
||||
@@ -73,7 +72,6 @@ import java.util.Map;
|
||||
BlobModule.class,
|
||||
FileReaderModule.class,
|
||||
AsyncStorageModule.class,
|
||||
CameraRollManager.class,
|
||||
ClipboardModule.class,
|
||||
DatePickerDialogModule.class,
|
||||
DialogModule.class,
|
||||
@@ -118,8 +116,6 @@ public class MainReactPackage extends TurboReactPackage {
|
||||
return new FileReaderModule(context);
|
||||
case AsyncStorageModule.NAME:
|
||||
return new AsyncStorageModule(context);
|
||||
case CameraRollManager.NAME:
|
||||
return new CameraRollManager(context);
|
||||
case ClipboardModule.NAME:
|
||||
return new ClipboardModule(context);
|
||||
case DatePickerDialogModule.FRAGMENT_TAG:
|
||||
@@ -203,7 +199,6 @@ public class MainReactPackage extends TurboReactPackage {
|
||||
BlobModule.class,
|
||||
FileReaderModule.class,
|
||||
AsyncStorageModule.class,
|
||||
CameraRollManager.class,
|
||||
ClipboardModule.class,
|
||||
DatePickerDialogModule.class,
|
||||
DialogModule.class,
|
||||
|
||||
Reference in New Issue
Block a user