NativeScript / plugins

@nativescript plugins to help with your developments.
https://docs.nativescript.org/plugins/index.html
Apache License 2.0
189 stars 107 forks source link

[@nativescript/imagepicker] - app crashes on video selection that is unloaded to iCloud #497

Open andrewm-mitchells opened 1 year ago

andrewm-mitchells commented 1 year ago

Using @nativescript/imagepicker When I pick a video that's stored locally it works as expected but if the video is old and is unloaded to iCloud to save device's storage it crashes the app immediately with the following error:

authorizing...
  ***** Fatal JavaScript exception - application has been terminated. *****
  NativeScript encountered a fatal error: Uncaught Error: The file couldn\M-b\M^@\M^Z\M-C\M^D\M-C\M-4t be opened.
  at
  _loop_2(file: src/webpack:/mitch/node_modules/@nativescript/imagepicker/index.ios.js:148:0)
  at (file: src/webpack:/mitch/node_modules/@nativescript/imagepicker/index.ios.js:158:0)
  at timer(file: src/webpack:/mitch/node_modules/zone.js/fesm2015/zone.js:2405:0)
  at invokeTask(file: src/webpack:/mitch/node_modules/zone.js/fesm2015/zone.js:406:0)
  at runTask(file: src/webpack:/mitch/node_modules/zone.js/fesm2015/zone.js:178:0)
  at invokeTask(file: src/webpack:/mitch/node_modules/zone.js/fesm2015/zone.js:487:0)
  at ZoneTask.invoke(file: src/webpack:/mitch/node_modules/zone.js/fesm2015/zone.js:476:0)
  at data.args.<computed>(file: src/webpack:/mitch/node_modules/zone.js/fesm2015/zone.js:2385:0)
  at invoke(file: src/webpack:/mitch/node_modules/@nativescript/core/timer/index.ios.js:54:0)
  at invoke(file: src/webpack:/mitch/node_modules/zone.js/fesm2015/zone.js:372:0)
  at runGuarded(file: src/webpack:/mitch/node_modules/zone.js/fesm2015/zone.js:144:0)
  at (file: src/webpack:/mitch/node_modules/zone.js/fesm2015/zone.js:128:0)
  at TimerTargetImpl.tick(file: src/webpack:/mitch/node_modules/@nativescript/core/timer/index.ios.js:18:0)

and this is how I'm handling video selection:

if (result === "Add video") {
        this.mediaType = "video";
        let videoPicker = imagePickerPlugin.create({
          mode: 'single',
          mediaType: ImagePickerMediaType.Video
        });

        this.startVideoSelection(videoPicker);
      }

---

startVideoSelection(videoPicker) {
    videoPicker
      .authorize()
      .then(() => videoPicker.present())
      .then((selection) => {
        console.log('Selection done: ' + JSON.stringify(selection));

        this.processVideo(selection);
      })
      .catch((err) => {
        console.log(err);
        // this.errorMessage = err;
      });
  }
punndcoder28 commented 1 year ago

Hey. From my experience with image picker packages, are they not supposed to only work with local data? When you are uploading to iCloud to save device storage the media file does not exist on the device which could be leading to the crash. Maybe the package maintainers can shed more light if I am correct and I am happy to work on a fix if possible.

andrewm-mitchells commented 1 year ago

Hey. From my experience with image picker packages, are they not supposed to only work with local data? When you are uploading to iCloud to save device storage the media file does not exist on the device which could be leading to the crash. Maybe the package maintainers can shed more light if I am correct and I am happy to work on a fix if possible.

I'd think even if can't trigger the download it should not crash. When selecting videos using v1.x from NS Preview app it shows only two videos on my iPhone while I have hundreds. I can't find anything in docs that would allow limiting selection to only on-device assets or how to handle iCloud stored images/videos.

andrewm-mitchells commented 1 year ago

It appears that setting copyToAppFolder: 'media' on

let videoPicker = imagePickerPlugin.create({
          mode: 'single',
          mediaType: ImagePickerMediaType.Video
});

can help avoid app crash. Crashed only once during my testing in picking years old videos. But ideally there should be a way to handle this case as part of the plugin or at lease a reference to another resource in the docs how this can be achieved as this is a real world case that majority of iOS users will have.

punndcoder28 commented 1 year ago

From the docs, I can see that copyToAppFolder creates a new folder in the app. So are the videos being downloaded from iCloud and then being picked?

andrewm-mitchells commented 1 year ago

No, it returns error Error copying file: Error: file is directory and there's no path prop on selected asset

punndcoder28 commented 1 year ago

Oh OK. Even I do not know the internal workings. Will wait for a reply from the maintainers of the repo.

raphaelnm commented 11 months ago

Hey! Has anyone found a solution for this one? I am finding the same issue when trying to pick old images. @andrewm-mitchells did you find any solution?

andrewm-mitchells commented 11 months ago

Hey! Has anyone found a solution for this one? I am finding the same issue when trying to pick old images. @andrewm-mitchells did you find any solution?

This is a workaround I've been using for iOS. So far haven't found any alternative for Android so it's available only to iOS user of our app.

...

declare const PHVideoRequestOptions;
declare const PHVideoRequestOptionsVersion;
declare const PHImageManager;
declare const AVAsset;
declare const AVAudioMix;
declare const NSDictionary;

@Component({

...

iosVideoPicker(): void {
    const promise = new Promise(function (resolve, reject) {
      let context = imagepicker.create({
        mode: "single",
        mediaType: 2
      });

      context.authorize().then(() => {
        return context.present();
      }).then((selection) => {
        let results = [];

        for (let i = 0; i < selection.length; i++) {
          const selected = selection[i];
          const ios = selected.ios;
          const opt = PHVideoRequestOptions.new();
          opt.version = PHVideoRequestOptionsVersion.Current;

          PHImageManager.defaultManager().requestAVAssetForVideoOptionsResultHandler(
            ios, opt, (asset: typeof AVAsset, audioMix: typeof AVAudioMix, info: typeof NSDictionary) => {
              if (asset !== null) {
                const regex = /(file[^>]*)/g
                const file = asset.toString().match(regex)[0];
                const name = file.substr(file.lastIndexOf("/") + 1);
                const mimeType = name.substr(name.lastIndexOf('.')+1);
                const filename = (new Date).getTime().toString() + "." + mimeType;
                const new_path = path.join(knownFolders.documents().path, "PDI");
                const folder = Folder.fromPath(new_path);
                const filePath = path.join(folder.path, filename);

                getFile(file, filePath).then(() => {

                },
                (err) => {
                  this.errorMessage = err;
                });

                results.push(filePath);

                if (i == selection.length - 1) {
                  resolve(results);
                }
              } else {
                reject();
              }
            });
        };
      },
      (err) => {});
    }).catch((err) => {
      this.errorMessage = err;
    });

    promise.then((file) => {
      if (file !== undefined) {
        this.errorMessage = "";

        this.getPresignedUrl(file[0], "mov");
      } else {
        throw new Error("Unfortunately this file can not be used.");
      }
    })
    .catch((err) => {
      this.errorMessage = err;
    });
  }
raphaelnm commented 11 months ago

Thanks for sharing. I found out that the workaround suggested by you on May 18, which is seeting copyToAppFolder: 'media', works for images.