Skip to content

Commit 3ae287c

Browse files
authored
fix: Resolve OutOfMemoryError in image/video processing (#2137)
* fix: Resolve OutOfMemoryError in image/video processing * fix: resolve OutOfMemoryError in media processing * fix: preserve file modification date during media processing
1 parent aa64f71 commit 3ae287c

File tree

2 files changed

+35
-27
lines changed

2 files changed

+35
-27
lines changed

android/src/main/java/com/reactnative/ivpusic/imagepicker/Compression.java

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,7 @@ File resize(
3535
int maxWidth,
3636
int maxHeight,
3737
int quality
38-
) throws IOException {
38+
) throws IOException,OutOfMemoryError {
3939
Pair<Integer, Integer> targetDimensions =
4040
this.calculateTargetDimensions(originalWidth, originalHeight, maxWidth, maxHeight);
4141

@@ -105,7 +105,7 @@ private boolean shouldSetOrientation(String orientation) {
105105
&& !orientation.equals(String.valueOf(ExifInterface.ORIENTATION_UNDEFINED));
106106
}
107107

108-
File compressImage(final Context context, final ReadableMap options, final String originalImagePath, final BitmapFactory.Options bitmapOptions) throws IOException {
108+
File compressImage(final Context context, final ReadableMap options, final String originalImagePath, final BitmapFactory.Options bitmapOptions) throws IOException,OutOfMemoryError {
109109
Integer maxWidth = options.hasKey("compressImageMaxWidth") ? options.getInt("compressImageMaxWidth") : null;
110110
Integer maxHeight = options.hasKey("compressImageMaxHeight") ? options.getInt("compressImageMaxHeight") : null;
111111
Double quality = options.hasKey("compressImageQuality") ? options.getDouble("compressImageQuality") : null;

android/src/main/java/com/reactnative/ivpusic/imagepicker/PickerModule.java

Lines changed: 33 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -73,6 +73,7 @@ class PickerModule extends ReactContextBaseJavaModule implements ActivityEventLi
7373
private static final String E_CAMERA_IS_NOT_AVAILABLE = "E_CAMERA_IS_NOT_AVAILABLE";
7474
private static final String E_CANNOT_LAUNCH_CAMERA = "E_CANNOT_LAUNCH_CAMERA";
7575
private static final String E_ERROR_WHILE_CLEANING_FILES = "E_ERROR_WHILE_CLEANING_FILES";
76+
private static final String E_LOW_MEMORY_ERROR = "E_LOW_MEMORY_ERROR";
7677

7778
private static final String E_NO_LIBRARY_PERMISSION_KEY = "E_NO_LIBRARY_PERMISSION";
7879
private static final String E_NO_LIBRARY_PERMISSION_MSG = "User did not grant library permission.";
@@ -368,28 +369,21 @@ private void initiateCamera(Activity activity) {
368369
private void initiatePicker(final Activity activity) {
369370
try {
370371
PickVisualMediaRequest.Builder builder = new PickVisualMediaRequest.Builder();
371-
PickVisualMediaRequest request = new PickVisualMediaRequest();
372-
373-
if (cropping || mediaType.equals("photo")) {
374-
request = builder.setMediaType(new ActivityResultContracts.PickVisualMedia.SingleMimeType("image/*")).build();
375-
}
376-
else{
377-
if (cropping) {
378-
request = builder.setMediaType(ActivityResultContracts.PickVisualMedia.ImageOnly.INSTANCE).build();
379-
}
380-
else if (mediaType.equals("video")) {
381-
request = builder.setMediaType(ActivityResultContracts.PickVisualMedia.VideoOnly.INSTANCE).build();
372+
// Simplified media type handling
373+
if (mediaType.equals("video")) {
374+
builder.setMediaType(ActivityResultContracts.PickVisualMedia.VideoOnly.INSTANCE);
375+
} else if (mediaType.equals("photo") || cropping) {
376+
// Force image-only for cropping
377+
builder.setMediaType(ActivityResultContracts.PickVisualMedia.ImageOnly.INSTANCE);
382378
} else {
383-
request = builder.setMediaType(ActivityResultContracts.PickVisualMedia.ImageAndVideo.INSTANCE).build();
384-
}
379+
builder.setMediaType(ActivityResultContracts.PickVisualMedia.ImageAndVideo.INSTANCE);
385380
}
386381

387382
Intent intent;
388-
389383
if (multiple) {
390-
intent = new ActivityResultContracts.PickMultipleVisualMedia().createIntent(activity, request);
384+
intent = new ActivityResultContracts.PickMultipleVisualMedia().createIntent(activity, builder.build());
391385
} else {
392-
intent = new ActivityResultContracts.PickVisualMedia().createIntent(activity, request);
386+
intent = new ActivityResultContracts.PickVisualMedia().createIntent(activity, builder.build());
393387
}
394388

395389
activity.startActivityForResult(intent, IMAGE_PICKER_REQUEST);
@@ -515,15 +509,16 @@ private void getAsyncSelection(final Activity activity, Uri uri, boolean isCamer
515509
resultCollector.notifySuccess(getImage(activity, path));
516510
}
517511

518-
private Bitmap validateVideo(String path) throws Exception {
512+
private Bitmap validateVideo(Uri uri) throws Exception {
519513
MediaMetadataRetriever retriever = new MediaMetadataRetriever();
520-
retriever.setDataSource(path);
514+
retriever.setDataSource(getCurrentActivity(), uri);
521515
Bitmap bmp = retriever.getFrameAtTime();
522516

523517
if (bmp == null) {
524518
throw new Exception("Cannot retrieve video data");
525519
}
526520

521+
retriever.release();
527522
return bmp;
528523
}
529524

@@ -540,7 +535,7 @@ private static Long getVideoDuration(String path) {
540535
}
541536

542537
private void getVideo(final Activity activity, final String path, final String mime) throws Exception {
543-
validateVideo(path);
538+
validateVideo(Uri.parse(path));
544539
final String compressedVideoPath = getTmpDir(activity) + "/" + UUID.randomUUID().toString() + ".mp4";
545540

546541
new Thread(new Runnable() {
@@ -550,21 +545,24 @@ public void run() {
550545
@Override
551546
public void invoke(Object... args) {
552547
String videoPath = (String) args[0];
553-
554548
try {
555-
Bitmap bmp = validateVideo(videoPath);
556-
long modificationDate = new File(videoPath).lastModified();
549+
File file = new File(videoPath);
550+
Uri videoUri = Uri.fromFile(file);
551+
MediaMetadataRetriever retriever = new MediaMetadataRetriever();
552+
retriever.setDataSource(activity, videoUri);
553+
Bitmap bmp = retriever.getFrameAtTime();
557554
long duration = getVideoDuration(videoPath);
558555

559556
WritableMap video = new WritableNativeMap();
560557
video.putInt("width", bmp.getWidth());
561558
video.putInt("height", bmp.getHeight());
562559
video.putString("mime", mime);
563-
video.putInt("size", (int) new File(videoPath).length());
560+
video.putInt("size", (int) file.length());
564561
video.putInt("duration", (int) duration);
565562
video.putString("path", "file://" + videoPath);
566-
video.putString("modificationDate", String.valueOf(modificationDate));
563+
video.putString("modificationDate", String.valueOf(file.lastModified()));
567564

565+
retriever.release();
568566
resultCollector.notifySuccess(video);
569567
} catch (Exception e) {
570568
resultCollector.notifyProblem(E_NO_IMAGE_DATA_FOUND, e);
@@ -596,6 +594,11 @@ private String resolveRealPath(Activity activity, Uri uri, boolean isCamera) thr
596594
}
597595

598596
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
597+
// For videos, get the real path but don't copy the file
598+
String mimeType = activity.getContentResolver().getType(uri);
599+
if (mimeType != null && mimeType.startsWith("video/")) {
600+
return RealPathUtil.getRealPathFromURI(activity, uri);
601+
}
599602

600603
String externalCacheDirPath = Uri.fromFile(activity.getExternalCacheDir()).getPath();
601604
String externalFilesDirPath = Uri.fromFile(activity.getExternalFilesDir(null)).getPath();
@@ -873,7 +876,12 @@ private void croppingResult(Activity activity, final int requestCode, final int
873876
if (resultUri != null) {
874877
try {
875878
if (width > 0 && height > 0) {
876-
File resized = compression.resize(this.reactContext, resultUri.getPath(), width, height, width, height, 100);
879+
File resized = null;
880+
try{
881+
resized = compression.resize(this.reactContext, resultUri.getPath(), width, height, width, height, 100);
882+
} catch (OutOfMemoryError ex) {
883+
resultCollector.notifyProblem(E_LOW_MEMORY_ERROR, ex.getMessage());
884+
}
877885
resultUri = Uri.fromFile(resized);
878886
}
879887

0 commit comments

Comments
 (0)