Skip to content

Commit

Permalink
[FEATURE] Video support
Browse files Browse the repository at this point in the history
  • Loading branch information
ivpusic committed Sep 11, 2016
1 parent 3b7f2c4 commit 457c0ef
Show file tree
Hide file tree
Showing 13 changed files with 495 additions and 107 deletions.
6 changes: 3 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
# react-native-image-crop-picker
iOS/Android image picker with support for camera, multiple images and cropping
iOS/Android image picker with support for camera, video compression, multiple images and cropping

## Result

Expand Down Expand Up @@ -68,6 +68,7 @@ ImagePicker.clean().then(() => {
| multiple | bool (default false) | Enable or disable multiple image selection |
| includeBase64 | bool (default false) | Enable or disable returning base64 data with image |
| maxFiles (ios only) | number (default 5) | Max number of files to select when using `multiple` option |
| compressVideo (ios only) | number (default true) | When video is selected, compress it and convert it to mp4 |

#### Response Object

Expand Down Expand Up @@ -95,7 +96,7 @@ react-native link react-native-image-crop-picker

- Add `platform :ios, '8.0'` to Podfile (!important)
- Add `pod 'RSKImageCropper'` and `pod 'QBImagePickerController'` to Podfile

###### non-cocoapods users

- Drag and drop the ios/ImageCropPickerSDK folder to your xcode project. (Make sure Copy items if needed IS ticked)
Expand Down Expand Up @@ -141,4 +142,3 @@ It is basically wrapper around few libraries

## License
*MIT*

2 changes: 1 addition & 1 deletion android/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -16,5 +16,5 @@ android {

dependencies {
compile 'com.facebook.react:react-native:+'
compile 'com.yalantis:ucrop:2.1.2'
compile 'com.yalantis:ucrop:2.2.0-native'
}
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.graphics.PixelFormat;
import android.media.MediaMetadataRetriever;
import android.net.Uri;
import android.os.Build;
import android.provider.DocumentsContract;
Expand All @@ -28,6 +29,7 @@

import android.support.v4.app.ActivityCompat;
import android.content.pm.PackageManager;
import android.webkit.MimeTypeMap;

import com.yalantis.ucrop.UCrop;

Expand Down Expand Up @@ -58,6 +60,7 @@ public class PickerModule extends ReactContextBaseJavaModule implements Activity
private static final String E_CAMERA_IS_NOT_AVAILABLE = "E_CAMERA_IS_NOT_AVAILABLE";
private static final String E_CANNOT_LAUNCH_CAMERA = "E_CANNOT_LAUNCH_CAMERA";
private static final String E_PERMISSIONS_MISSING = "E_PERMISSIONS_MISSING";
private static final String E_ERROR_WHILE_CLEANING_FILES = "E_ERROR_WHILE_CLEANING_FILES";

private Promise mPickerPromise;
private Activity activity;
Expand All @@ -77,6 +80,15 @@ public PickerModule(ReactApplicationContext reactContext) {
mReactContext = reactContext;
}

public String getTmpDir() {
String tmpDir = mReactContext.getCacheDir() + "/react-native-image-crop-picker";
Boolean created = new File(tmpDir).mkdir();

System.out.println(tmpDir);

return tmpDir;
}

@Override
public String getName() {
return "ImageCropPicker";
Expand All @@ -90,14 +102,52 @@ private void setConfiguration(final ReadableMap options) {
cropping = options.hasKey("cropping") ? options.getBoolean("cropping") : cropping;
}

private void deleteRecursive(File fileOrDirectory) {
if (fileOrDirectory.isDirectory()) {
for (File child : fileOrDirectory.listFiles()) {
deleteRecursive(child);
}
}

fileOrDirectory.delete();
}

@ReactMethod
public void clean(final Promise promise) {
promise.resolve(null);
try {
File file = new File(this.getTmpDir());
if (!file.exists()) throw new Exception("File does not exist");

this.deleteRecursive(file);
promise.resolve(null);
} catch (Exception ex) {
ex.printStackTrace();
promise.reject(E_ERROR_WHILE_CLEANING_FILES, ex.getMessage());
}
}

@ReactMethod
public void cleanSingle(final String path, final Promise promise) {
promise.resolve(null);
public void cleanSingle(String path, final Promise promise) {
if (path == null) {
promise.reject(E_ERROR_WHILE_CLEANING_FILES, "Cannot cleanup empty path");
return;
}

try {
final String filePrefix = "file://";
if (path.startsWith(filePrefix)) {
path = path.substring(filePrefix.length());
}

File file = new File(path);
if (!file.exists()) throw new Exception("File does not exist. Path: " + path);

this.deleteRecursive(file);
promise.resolve(null);
} catch (Exception ex) {
ex.printStackTrace();
promise.reject(E_ERROR_WHILE_CLEANING_FILES, ex.getMessage());
}
}

@ReactMethod
Expand Down Expand Up @@ -159,7 +209,13 @@ public void openPicker(final ReadableMap options, final Promise promise) {

try {
final Intent galleryIntent = new Intent(Intent.ACTION_PICK);
galleryIntent.setType("image/*");

if (cropping) {
galleryIntent.setType("image/*");
} else {
galleryIntent.setType("image/*,video/*");
}

galleryIntent.putExtra(Intent.EXTRA_ALLOW_MULTIPLE, multiple);
galleryIntent.setAction(Intent.ACTION_GET_CONTENT);
galleryIntent.putExtra(Intent.EXTRA_LOCAL_ONLY, true);
Expand Down Expand Up @@ -198,28 +254,70 @@ private String getBase64StringFromFile(String absoluteFilePath) {
return Base64.encodeToString(bytes, Base64.NO_WRAP);
}

private WritableMap getImage(Uri uri, boolean resolvePath) {
public static String getMimeType(String url) {
String type = null;
String extension = MimeTypeMap.getFileExtensionFromUrl(url);
if (extension != null) {
type = MimeTypeMap.getSingleton().getMimeTypeFromExtension(extension);
}

return type;
}

public WritableMap getSelection(Uri uri) throws Exception {
String path = RealPathUtil.getRealPathFromURI(activity, uri);
if (path == null || path.isEmpty()) {
throw new Exception("Cannot resolve image path.");
}

String mime = getMimeType(path);
if (mime != null && mime.startsWith("video/")) {
return getVideo(path, mime);
}

return getImage(uri, true);
}

public WritableMap getVideo(String path, String mime) {
WritableMap image = new WritableNativeMap();

MediaMetadataRetriever retriever = new MediaMetadataRetriever();
retriever.setDataSource(path);
Bitmap bmp = retriever.getFrameAtTime();

if (bmp != null) {
image.putInt("width", bmp.getWidth());
image.putInt("height", bmp.getHeight());
}

image.putString("path", "file://" + path);
image.putString("mime", mime);
image.putInt("size", (int) new File(path).length());

return image;
}

private WritableMap getImage(Uri uri, boolean resolvePath) throws Exception {
WritableMap image = new WritableNativeMap();
String path = uri.getPath();

if (resolvePath) {
path = RealPathUtil.getRealPathFromURI(activity, uri);
}

if (path == null || path.isEmpty()) {
throw new Exception("Cannot resolve image path.");
}

BitmapFactory.Options options = new BitmapFactory.Options();
options.inJustDecodeBounds = true;

long fileLen = 0;
if (path != null) {
fileLen = new File(path).length();
}

BitmapFactory.decodeFile(path, options);
image.putString("path", "file://" + path);
image.putInt("width", options.outWidth);
image.putInt("height", options.outHeight);
image.putString("mime", options.outMimeType);
image.putInt("size", (int) fileLen);
image.putInt("size", (int) new File(path).length());

if (includeBase64) {
image.putString("data", getBase64StringFromFile(path));
Expand All @@ -232,7 +330,7 @@ public void startCropping(Uri uri) {
UCrop.Options options = new UCrop.Options();
options.setCompressionFormat(Bitmap.CompressFormat.JPEG);

UCrop.of(uri, Uri.fromFile(new File(activity.getCacheDir(), UUID.randomUUID().toString() + ".jpg")))
UCrop.of(uri, Uri.fromFile(new File(this.getTmpDir(), UUID.randomUUID().toString() + ".jpg")))
.withMaxResultSize(width, height)
.withAspectRatio(width, height)
.withOptions(options)
Expand All @@ -251,23 +349,36 @@ public void imagePickerResult(final int requestCode, final int resultCode, final
ClipData clipData = data.getClipData();
WritableArray result = new WritableNativeArray();

// only one image selected
if (clipData == null) {
result.pushMap(getImage(data.getData(), true));
} else {
for (int i = 0; i < clipData.getItemCount(); i++) {
result.pushMap(getImage(clipData.getItemAt(i).getUri(), true));
try {
// only one image selected
if (clipData == null) {
result.pushMap(getSelection(data.getData()));
} else {
for (int i = 0; i < clipData.getItemCount(); i++) {
result.pushMap(getSelection(clipData.getItemAt(i).getUri()));
}
}

mPickerPromise.resolve(result);
} catch (Exception ex) {
mPickerPromise.reject(E_NO_IMAGE_DATA_FOUND, ex.getMessage());
}

mPickerPromise.resolve(result);
} else {
Uri uri = data.getData();

if (cropping && uri != null) {
if (uri == null) {
mPickerPromise.reject(E_NO_IMAGE_DATA_FOUND, "Cannot resolve image url");
}

if (cropping) {
startCropping(uri);
} else {
mPickerPromise.resolve(getImage(uri, true));
try {
mPickerPromise.resolve(getSelection(uri));
} catch (Exception ex) {
mPickerPromise.reject(E_NO_IMAGE_DATA_FOUND, ex.getMessage());
}
}
}
}
Expand All @@ -280,15 +391,24 @@ public void cameraPickerResult(final int requestCode, final int resultCode, fina

if (resultCode == Activity.RESULT_CANCELED) {
mPickerPromise.reject(E_PICKER_CANCELLED_KEY, E_PICKER_CANCELLED_MSG);
} else if (resultCode == Activity.RESULT_OK && mCameraCaptureURI != null) {
} else if (resultCode == Activity.RESULT_OK) {
Uri uri = mCameraCaptureURI;

if (uri == null) {
mPickerPromise.reject(E_NO_IMAGE_DATA_FOUND, "Cannot resolve image url");
return;
}

if (cropping) {
UCrop.Options options = new UCrop.Options();
options.setCompressionFormat(Bitmap.CompressFormat.JPEG);
startCropping(uri);
} else {
mPickerPromise.resolve(getImage(uri, true));
try {
mPickerPromise.resolve(getImage(uri, true));
} catch (Exception ex) {
mPickerPromise.reject(E_NO_IMAGE_DATA_FOUND, ex.getMessage());
}
}
}
}
Expand All @@ -297,7 +417,11 @@ public void croppingResult(final int requestCode, final int resultCode, final In
if (data != null) {
final Uri resultUri = UCrop.getOutput(data);
if (resultUri != null) {
mPickerPromise.resolve(getImage(resultUri, false));
try {
mPickerPromise.resolve(getImage(resultUri, false));
} catch (Exception ex) {
mPickerPromise.reject(E_NO_IMAGE_DATA_FOUND, ex.getMessage());
}
} else {
mPickerPromise.reject(E_NO_IMAGE_DATA_FOUND, "Cannot find image data");
}
Expand Down Expand Up @@ -344,7 +468,7 @@ private boolean isCameraAvailable() {
private File createNewFile(final boolean forcePictureDirectory) {
String filename = "image-" + UUID.randomUUID().toString() + ".jpg";
if (tmpImage && (!forcePictureDirectory)) {
return new File(mReactContext.getCacheDir(), filename);
return new File(this.getTmpDir(), filename);
} else {
File path = Environment.getExternalStoragePublicDirectory(
Environment.DIRECTORY_PICTURES);
Expand Down
3 changes: 2 additions & 1 deletion example/android/app/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -88,7 +88,7 @@ android {

defaultConfig {
applicationId "com.example"
minSdkVersion 16
minSdkVersion 18
targetSdkVersion 22
versionCode 1
versionName "1.0"
Expand Down Expand Up @@ -126,6 +126,7 @@ android {
}

dependencies {
compile project(':react-native-video')
compile project(':react-native-image-crop-picker')
compile fileTree(dir: "libs", include: ["*.jar"])
compile "com.android.support:appcompat-v7:23.1.1"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
import android.app.Application;

import com.facebook.react.ReactApplication;
import com.brentvatne.react.ReactVideoPackage;
import com.facebook.react.ReactNativeHost;
import com.facebook.react.ReactPackage;
import com.facebook.react.shell.MainReactPackage;
Expand All @@ -23,6 +24,7 @@ protected boolean getUseDeveloperSupport() {
protected List<ReactPackage> getPackages() {
return Arrays.<ReactPackage>asList(
new MainReactPackage(),
new ReactVideoPackage(),
new PickerPackage()
);
}
Expand Down
1 change: 1 addition & 0 deletions example/android/app/src/main/res/values/strings.xml
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
<resources>

<string name="app_name">example</string>
</resources>
2 changes: 1 addition & 1 deletion example/android/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ buildscript {
jcenter()
}
dependencies {
classpath 'com.android.tools.build:gradle:2.1.0'
classpath 'com.android.tools.build:gradle:2.1.3'

// NOTE: Do not place your application dependencies here; they belong
// in the individual module build.gradle files
Expand Down
4 changes: 2 additions & 2 deletions example/android/gradle/wrapper/gradle-wrapper.properties
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
#Fri Aug 26 00:38:15 CEST 2016
#Sat Sep 10 03:05:34 CEST 2016
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-2.10-all.zip
distributionUrl=https\://services.gradle.org/distributions/gradle-2.14.1-all.zip
2 changes: 2 additions & 0 deletions example/android/settings.gradle
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
rootProject.name = 'example'

include ':app'
include ':react-native-video'
project(':react-native-video').projectDir = new File(rootProject.projectDir, '../node_modules/react-native-video/android')

include ':react-native-image-crop-picker'
project(':react-native-image-crop-picker').projectDir = new File(settingsDir, '../../android')
Loading

0 comments on commit 457c0ef

Please sign in to comment.