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鈥檒l occasionally send you account related emails.

Already on GitHub? Sign in to your account

[馃悰] launchImageLibrary promise takes too long to return video result on iOS. #2117

Closed
fixerloop opened this issue Apr 18, 2023 · 17 comments

Comments

@fixerloop
Copy link

No description provided.

@ratz6
Copy link
Contributor

ratz6 commented Apr 20, 2023

Picking Large Videos results in app crash also in android, and takes a lot of time on iOS (feels like app is stuck) > what's the best way to handle considering memory issues @Johan-dutoit @marcshilling @ravirajn22

Picker version > 5.3.1
RN version > 0.70.6

So what I see is after picking the video from device, it gets copied to app storage (temp storage) which increases the size of the app also and might lead to uncertain crashes once device is close to full memory.. Any suggestions how to handle these ? Youtube's size doesn't increase after picking, wonder how they do it..

@kusche12
Copy link

kusche12 commented May 8, 2023

I have also faced this issue for the past few months.

The function launchImageLibrary() works relatively quick for any video < 500mb, but for larger file sizes, it can take upwards of 15min to get the function to finish running!

Has anyone found a workaround for getting large video URI's in react native?

@ratz6
Copy link
Contributor

ratz6 commented May 10, 2023

I'm trying to use UIimagepickercontroller in swift directly and it plays the recording in a second screen after picking .. and once it's picked , in the second screen there is a progress loader of the video being processed/compressed ..
so if that works with react native , will stop using RNImagePicker for the time being..

I see a fn mapVideoToAsset being called here > https://github1s.com/react-native-image-picker/react-native-image-picker/blob/HEAD/ios/ImagePickerManager.mm#L230
which uses some temp directory and is that causing this issue ? @Johan-dutoit @marcshilling

Assuming the video file is first being ready from phone storage and been copied to App's temp storage which also increases the app size also and brings in a delay to process the Video so we don't the callback response until then ..

Any suggestions ?

ps: I get app crashes on android when video size is above 400-500 MB .. with memory exceptions..
can we pick a video in chunks ? and once the previous chunk is uploaded I delete it from my app using rnfs.unlink ?

How does youtube handle picking of such huge size video's so smoothly ?

image

https://support.google.com/youtube/answer/71673?hl=en&ref_topic=9257439&sjid=12828787696608343387-AP&co=GENIE.Platform%3DAndroid&oco=1

@ratz6
Copy link
Contributor

ratz6 commented May 18, 2023

found out a config which helped ..
phPickerConfig.preferredAssetRepresentationMode = .current
doing this on my native module of PHPicker I'm able to pick even 3 GB instantly and get the response instantly. Can we add a flag to enable this in this library ? @Johan-dutoit @marcshilling

my swift module >

import UIKit
import PhotosUI
import Photos
import AVFoundation
@available(iOS 14.0, *) // PHPicker works only after iOS version 14+
@objc(CustomImagePicker)
class CustomImagePicker: NSObject, PHPickerViewControllerDelegate {

  @objc static func requiresMainQueueSetup() -> Bool {
       return false
   }
  var window: UIWindow?
  var response = Dictionary<String, Any>()
  var finalPickerResponse = Dictionary<String,Any>()
  
  var resolve: RCTPromiseResolveBlock!
  var reject: RCTPromiseRejectBlock!

  @objc(openPicker:withResolver:withRejecter:)
  
  // UI for Picker
  func openPicker(options: NSDictionary, resolve:@escaping RCTPromiseResolveBlock,reject:@escaping RCTPromiseRejectBlock) -> Void {
    self.resolve = resolve;
    self.reject = reject;
    DispatchQueue.main.async {
      var phPickerConfig = PHPickerConfiguration(photoLibrary: .shared())
      phPickerConfig.selectionLimit = 1
      phPickerConfig.preferredAssetRepresentationMode = .current
      phPickerConfig.filter = PHPickerFilter.any(of: [.videos])
           let phPickerVC = PHPickerViewController(configuration: phPickerConfig)
           phPickerVC.delegate = self
      self.getTopMostViewController()?.present(phPickerVC, animated: true, completion: nil)
      }
    }
  func getTopMostViewController() -> UIViewController? {
      var topMostViewController = UIApplication.shared.keyWindow?.rootViewController
      while let presentedViewController = topMostViewController?.presentedViewController {
          topMostViewController = presentedViewController
      }
      return topMostViewController
  }
  
  // get video dimensions
  func initAspectRatioOfVideo(with fileURL: URL) -> Array<Any> {
    let resolution = resolutionForLocalVideo(url: fileURL)
    guard let width = resolution?.width, let height = resolution?.height else {
       return [0]
    }
    return [width , height]
  }

  private func resolutionForLocalVideo(url: URL) -> CGSize? {
      guard let track = AVURLAsset(url: url).tracks(withMediaType: AVMediaType.video).first else { return nil }
     let size = track.naturalSize.applying(track.preferredTransform)
     return CGSize(width: abs(size.width), height: abs(size.height))
  }
  
  // get video size in MB
  func fileSizeInMB(url: URL?) -> Double {
      guard let filePath = url?.path else {
          return 0.0
      }
      do {
          let attribute = try FileManager.default.attributesOfItem(atPath: filePath)
          if let size = attribute[FileAttributeKey.size] as? NSNumber {
              return size.doubleValue / 1000000.0
          }
      } catch {
          print("Error: \(error)")
      }
      return 0.0
  }
  
  struct ScopedResourceFile {
      let name: String
      let data: Data
  }
  
  
  // copying the file from Pickers symbolic temp directory to app's sandbox memory.
  // though the url is coming ot to be the same without copying unable to read the file
  func createTemporaryURLforVideoFile(url: NSURL) -> NSURL {
      let temporaryDirectoryURL = URL(fileURLWithPath: NSTemporaryDirectory(), isDirectory: true)
      let temporaryFileURL = temporaryDirectoryURL.appendingPathComponent(url.lastPathComponent ?? "")
      print("temporaryFileURL",temporaryFileURL)
      if(FileManager().fileExists(atPath: temporaryFileURL.path)){
        try? FileManager().removeItem(atPath: temporaryFileURL.path)
      }
      do {
          try FileManager().copyItem(at: url.absoluteURL!, to: temporaryFileURL)
      } catch {
          print("There was an error copying the video file to the temporary location.")
      }
      return temporaryFileURL as NSURL
  }

  // picker response
  func picker(_ picker: PHPickerViewController, didFinishPicking results: [PHPickerResult]) {
    picker.dismiss(animated: true)
    guard let provider = results.first?.itemProvider else {return}
    if provider.hasItemConformingToTypeIdentifier(UTType.movie.identifier){
      provider.loadFileRepresentation(forTypeIdentifier: UTType.movie.identifier){ [self] fileUrl,error in
        var assets :Array<Any> = []
//        response["uri"] = fileUrl?.absoluteString
        response["uri"] = createTemporaryURLforVideoFile(url: fileUrl! as NSURL).absoluteString
        response["fileName"] = fileUrl?.lastPathComponent
        response["type"] = fileUrl?.pathExtension
        response["height"] = initAspectRatioOfVideo(with : fileUrl!)[1]
        response["width"] = initAspectRatioOfVideo(with : fileUrl!)[0]
        response["duration"] = CMTimeGetSeconds(AVAsset(url: fileUrl!).duration)
        response["fileSize"] = fileSizeInMB(url:fileUrl)
        assets.insert(response, at: 0)
        finalPickerResponse["assets"] = assets
        print("finalPickerResponse",finalPickerResponse)
        resolve(finalPickerResponse)
      }
    }
  }
}

@Johan-dutoit
Copy link
Collaborator

Seems like a good idea!

@ratz6
Copy link
Contributor

ratz6 commented May 23, 2023

Any workaround for picking and playing large videos on android ?
We tried directly using the File path instead of copying to app's cache but that also didn't improve the video picking time

Image Picker becomes very slow making the app unresponsive and crashes sometime or if i get result after the wait, React native video on exoPlayer throws outOfMemory issues for large videos..

Let me know if you have any experience in this @Johan-dutoit @marcshilling @cobarx @c-moyer @freeboub

@c-moyer
Copy link

c-moyer commented May 23, 2023

Any workaround for picking and playing large videos on android ?

We tried directly using the File path instead of copying to app's cache but that also didn't improve the video picking time

Image Picker becomes very slow making the app unresponsive and crashes sometime or if i get result after the wait, React native video on exoPlayer throws outOfMemory issues for large videos..

Let me know if you have any experience in this @Johan-dutoit @marcshilling @cobarx @c-moyer @freeboub

Can you post an example of your react native code please?

@dwosk
Copy link

dwosk commented May 23, 2023

@Johan-dutoit @ratz6 would #2083 be what's needed here?

@ratz6
Copy link
Contributor

ratz6 commented May 24, 2023

@dwosk yes it solved the long wait time while picking videos for iOS. thanks, to point out the PR :)

@Johan-dutoit
Copy link
Collaborator

Changes merged. Closing for now.

@asdolo
Copy link

asdolo commented Jun 30, 2023

@dwosk yes it solved the long wait time while picking videos for iOS. thanks, to point out the PR :)

What value of assetRepresentationMode did you use to solve this issue?

@Taimoor20
Copy link

@ratz6 solution worked in my case. Thanks man, you saved my day.

@viniciusaugutisps
Copy link

viniciusaugutisps commented Sep 1, 2023

Hi, for me it didn't work. Is it correct like this?
Still slow to return callback for IOS
image

@ratz6
Copy link
Contributor

ratz6 commented Sep 2, 2023

@viniciusaugutisps what is the file type and file size ?

So the time comes in 2 cases :

  1. iOS 14 and above for PHPicker
  2. when we pick a large .mov video and ask it to be converted to .mp4 .. it takes time. You can modularise picking and conversion separately and show it in Ui accordingly, even though picking will be instant now.

You can also try out with a custom swift module pasted above, that should do the work

@viniciusaugutisps
Copy link

viniciusaugutisps commented Sep 5, 2023

@ratz6 the file I'm trying to upload is a 54mb hevc video and it takes about 20 seconds for the image picker callback to return and for me to be able to include a loading for the user.

@stomataln
Copy link

I think this issue isn't completely solved. When user picks a video which is in iCloud, it will still take a long time for the picker promise to respond.
I checked the PhPicker and seems like it's an issue on their end and they don't provide any progress and status that can help this...
I just hope there is a way for me to know when the image picker is offscreen, at least by then I know the picker is gone but still trying to fetch what user selects.

@99-digital
Copy link

I had trouble uploading a MOV video, which took a long time to convert to MP4.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests