apache / cordova-plugin-file

Apache Cordova File Plugin
https://cordova.apache.org/
Apache License 2.0
743 stars 762 forks source link

File Download and Access Issues on iOS 17.5.1 with Cordova #628

Open andreafrancioni opened 3 months ago

andreafrancioni commented 3 months ago

Versions:

•   iOS Version: 17.5.1
•   Cordova Version: 12.0.0 (cordova-lib@12.0.1)
•   Cordova Plugin File Version: 8.1.0

Description:

I am experiencing an issue with downloading and accessing media files on iOS 17.5.1 using Cordova. The code works perfectly on Android devices, but on iOS, the files are downloaded but cannot be accessed or played.

Steps to Reproduce:

1.  Prompt the user to download media files.
2.  Download the files and save them in the cache directory using cordova.file.dataDirectory.
3.  Attempt to access and play the downloaded files.

Current Behavior:

On iOS, the files are downloaded successfully, but when attempting to access or play these files, the app fails to locate the files, and they appear to be missing or inaccessible.

Expected Behavior:

The downloaded files should be accessible and playable from the cache directory, similar to the behavior observed on Android devices.

Relevant Code:

setDialog( "E' necessario scaricare i contenuti audio/video, vuoi continuare?", "yes-no", (answer) => { if (answer === "yes") { this.downloadMediaFiles(); } } );

getCacheDirectoryPath() { // Ottieni il percorso della directory di cache window.resolveLocalFileSystemURL(cordova.file.dataDirectory, (dirEntry) => { this.cacheDirectory = dirEntry.toURL(); console.log("Cache directory: " + this.cacheDirectory); }, (error) => { console.error("Errore nel risolvere il file system: " + error.toString()); }); }

async downloadMediaFiles() { this.downloadProgress = 0; this.downloadMessage = Scaricamento di 0/${this.mediaFiles.length} file;

for (let i = 0; i < this.mediaFiles.length; i++) { await this.downloadFile(this.server_base_path + this.mediaFiles[i]); this.downloadProgress = ((i + 1) / this.mediaFiles.length) * 100; this.downloadMessage = Scaricamento di ${i + 1}/${this.mediaFiles.length} file; }

this.downloadMessage = "Download completato!"; localStorage.setItem('downloadedMediaFiles', JSON.stringify(this.mediaFiles));

setTimeout(() => { this.showDownloadDialog = false; }, 300); }

async downloadFile(url) { console.log("Download URL:", url); try { const response = await fetch(url); if (!response.ok) { throw new Error('Network response was not ok'); } const blob = await response.blob(); const fileName = url.split('/').pop(); console.log("File name:", fileName);

window.resolveLocalFileSystemURL(this.cacheDirectory, (dirEntry) => {
  dirEntry.getFile(fileName, { create: true, exclusive: false }, (fileEntry) => {
    fileEntry.createWriter((fileWriter) => {
      fileWriter.onwriteend = () => {
        console.log("Download completato: " + fileEntry.toURL());
      };
      fileWriter.onerror = (error) => {
        console.error("Errore nel download del file: " + error.toString());
      };
      console.log(blob);
      fileWriter.write(blob);
    }, (error) => {
      console.error("Errore nella creazione del file: " + error.toString());
    });
  }, (error) => {
    console.error("Errore nel recuperare il file: " + error.toString());
  });
}, (error) => {
  console.error("Errore nel risolvere il file system: " + error.toString());
});

} catch (error) { console.error("Errore nel download del file: " + error.toString()); } }

video source are build like this: <video preload="auto" playinline="" autoplay="" controls="" controlslist="nofullscreen"><source src="file:///var/mobile/Containers/Data/Application/(APP_ID)/Library/NoCloud/(FILENAME).mp4" type="video/mp4"></video>

Observations:

•   The issue appears to be specific to iOS.
•   The file download and access process works correctly on Android devices.
•   When attempting to download a file through Safari’s debugger, it can be accessed correctly if saved manually.

Possible Causes:

•   Differences in file system access permissions between iOS and Android.
•   Possible bug in cordova-plugin-file or Cordova itself on iOS 17.5.1.

Any insights or suggestions on how to resolve this issue would be greatly appreciated. Specifically, if there are known issues with cordova-plugin-file on iOS 17.5.1 or if additional configurations are required, please let me know.

phynae commented 2 months ago

Same here! Working when reading and writing using the FileSystem-API but not when passing the file-url to for example a HTML-ImageTag. Furthermore my attempt was to convert it using the window.WkWebView.convertFilePath.

phynae commented 2 months ago

The following scenarios were tested, but the issue persisted:

Workaround (not recommended though):

Weird thing is, that when downloaded the already released version of the same app (release was about half a year ago), the issue is gone

It almost seems like it has to do with X-Code. Unfortunately it was not possible for me to check that.

breautek commented 2 months ago

If you're app is configured to use schemes (e.g. your html page is loaded over http(s)://... on android or app://... (the scheme is customizable on iOS) then using file:// paths in your DOM will likely get blocked by CORs. If you're app is loaded over the file:// protocol, then using file:// paths in the DOM should work as expected but is considered less secure.

v7 (if I recall correctly) of the file plugin introduce changes to produce a DOM-usable url when using the FileEntry.toURL() method.

The pasted code is not well formatted and is hard to read but here are some things to check:

  1. Make sure .toURL() is being used.
  2. The ionic webview may not parse the URL produced by .toURL() properly, so if the ionic webview is being used, attempt to test using the default cordova webview.

If it still doesn't work, please let us know what version of cordova-ios you're using and whether you're using schemes or loading directly the file:// protocol.

phynae commented 2 months ago

Hi, we are using cordova-ios@7.1.1. I've already tried using the .toURL function without success. If your assumption that the change was introduced in v7 is true, then i wonder why it didn't work when i build using an older version.

Furthermore, as mentioned above, we faced the same error when re-deploying old apps (that previously had no such issues) that use way older versions of cordova-ios onto iPhones running a pretty modern iOS-Version.

dele1907 commented 2 months ago

I also have the problem which @phynae mentioned above. Passing the file URL to an image tag is not working for me, but when I access files for reading or writing using the filesystem-API everything works fine.

I am operating on cordova-ios@7.1.1 with cordova@12.0.0. Additionally I have tried to get rid of the issue using older versions of cordova-ios@6.1.0, cordova-plugin-file@7.0.0 and cordova@10.0.0 but this will not change anything. Moreover the .toURL function also did not help me to solve the problem.

breautek commented 2 months ago

I was able to take a deeper look this morning and .toURL() on file entries is suppose to produce a DOM-usable url, but it won't on ios when using app schemes.

It's missing an implementation override the scheme task and to map a scheme URL to the file on disk to return an asset over the scheme.

Workarounds could include reading the asset as a blob and using blob urls, or if the asset is small enough, read as a data uri. Don't forget that blob urls needs to be released when it's not being used anymore.

If you're using a large asset like a video file, then there is no "good" workaround available afaik.

I can't really give any timeline on when a proper solution will be made available but it is on our radar now.

andreafrancioni commented 15 hours ago

Hi breautek, its been long time since last time i tried using cached file on ios, there's been new implementation or have you implemented new functionality about this problem?

Thank you for your patience

breautek commented 14 hours ago

This would be specific to iOS but I've been told that since the addition of schemes on iOS there is a WkWebView.convertFilePath API available in JS.

It's not ideal since you would have to do a platform check but you could try it as a work around.

I'm not familiar with the API, but I believe it would work something like:

let myImage = document.getElementById('myImage');
myImage.src = WkWebView.convertFilePath(theFilePath);