I am using the image-picker (cordova-imagePicker) plugin in order to get images from gallery and upload them to a server.
I am using Cordova 6.1.1 with Android platform 5.1.1 and the following plugins:
cordova-plugin-camera 2.2.0 "Camera"
cordova-plugin-compat 1.0.0 "Compat"
cordova-plugin-device 1.0.1 "Device"
cordova-plugin-file 4.2.0 "File"
cordova-plugin-imagepicker 1.1.0 "ImagePicker"
cordova-plugin-inappbrowser 1.4.0 "InAppBrowser"
cordova-plugin-media 2.3.0 "Media"
As callback to the plugin, I am converting the path I get to a File using the following code. Note that I use resolveFile because this code is running also in desktop in which case, the entry is already a File object.
var resolveFile = function(entry) {
if (typeof(entry) === "string") {
var deferred = $q.defer();
// first convert to local file system URL
window.resolveLocalFileSystemURL(entry, function(fileEntry) {
// now read/convert the file to file object.
fileEntry.file(function(file) {
console.log("File converted to file entry");
deferred.resolve(file);
}, function(err) {
console.log("Failed to convert to file entry", err);
deferred.reject(err);
});
}, function(err) {
console.log("Failed to resolve to file URL", err);
deferred.reject(err);
});
return deferred.promise;
} else {
return $q.when(entry);
}
};
This, in turn is used to read the image and pass it to a function that uploads it to the server ($files is what I am getting from plugin or from input in case of desktop/browser):
var upload = function () {
if (!$files[currentFile]) {
onAllFinished();
return;
}
file = $files[currentFile];
beforeLoad(file);
fileReader = new FileReader();
fileReader.onload = onload;
fileReader.onprogress = progress;
resolveFile(file).then(function(actualFile) {
fileReader.readAsDataURL(actualFile);
});
currentFile++;
};
In the above, onload cuts the image data (following 'base64,' in string) and sends it to the the upload code which expects a base64 string and uploads the data to the server using simple AJAX call:
var uploadPhoto = function(url, photo, callback, error)
$http.post(url, {
photo: photo,
})
.success(callback)
.error(function (data, status, headers, config) {
if (error)
error(data, status, headers, config);
});
The last function works also with the camera plugin camera plugin using DATA_URI target (I know, it's not recommended) which also return a base64 string (so I can reuse the code).
It seems to me there's something wrong going on with the file reader output (I am guessing). What (I think) hints to that is that small images (10s kb) are loaded fine as well as already prepared base64 string from camera plugin but larger images (few MBs) that goes through the filereader (on Android, on desktop it is fine) are uploaded corrupted (see below).
Has anyone run into such issues? Can anyone suggest a fix (other than changing the code to use FileTransfer plugin)?
The original image:
The uploaded (corrupted) image. Note, some of it is read/uploaded fine:
I found your question while searching for a solution for a similar problem. DataURL's of large images from camera would show up when used as the source of an image but the same image got corrupted when I use fileReader.readAsDataURL.
I've been able to bypass the problem by using fileReader.readAsBinaryData instead of fileReader.readAsDataURL and then turning the binarystring into a dataURL.
window.resolveLocalFileSystemURL(imageUri, function done(fileEntry) {
fileEntry.file(function (fileObj) {
var image = new Image();
var reader = new FileReader();
reader.onloadend = function (e) {
image.src = "data:image/jpeg;base64," + window.btoa(e.target.result)
}
reader.readAsBinaryString(fileObj);
}
}
Hopefully this helps you to find a workaround of your own.
Related
I am creating an App for Android using Cordova, and I would like to open and display a file (PDF or image) that is served from the server as Base64-encoded binary data.
Of course I have read the multiple other posts on the subject that already exist on this website, but none of the proposed solutions have worked for me, more details below.
To be more precise, the server sends a JSON-file to the app, which among many other things contains a string consisting of the base64-encoded contents of a PDF file. I want to convert this data back into the represented PDF and display it to the user.
If this were a pure browser page, I would simply package my base64 data into a data-URL, attach this as the href of some anchor, and add a download-attribute. Optionally I could wrap all of my data into a blob and create an object url for that first.
In Cordova, this does not work. Clicking the <a> does nothing. Here is what I have attempted so far:
Using the file plugin, I can write the binary data to a file on the device. This works, and using a terminal I can see that the file was downloaded correctly, but into an app-private directory which I cannot access normally (e.g. through the file explorer).
Accessing the user's "downloads" folder is blocked by the file system
Using window.open with the file path as the first argument and "_system" as the target does nothing. There is no error but also nothing happens. Setting the target to "_blank" instead, I get an error saying ACCESS_DENIED.
Using cordova.InAppBrowser behaves the same was as window.open
With the plugin file-opener2 installed, the app will not compile, because the plugin is looking for an android4 toolchain, and I am building for android 9 and up
The plugin document-viewer (restricting to PDFs for the time being) suffers the same problem and does not compile.
Passing the data-URI to window.open (or cordova.InAppBrowser) directly loads for a very long time and eventually tells me that the desired page could not be loaded.
The PDF file I am using for testing is roughly 17kb after converting to base64. I know this is technically above the spec for how long data-URIs can be, but Chrome in the browser has no trouble with it whatsoever, and using a much shorter URI (only a few dozen bytes) produces the same behavior.
Ideally, what I would like to do, is download the file and then trigger the user's standard browser to open the file itself. That was, I would not have to deal with MIME types and also it would look exactly how the user expected from their own device.
Alternatively, if that doesn't work, I would be ok with downloading the file into a system-wide directory and prompting the user to open it themselves. This is not optimal, but I would be able to swallow that pill.
And lastly, if there is a plugin or some other solution that solves the problem amazingly, but for PDFs only, then I can also work out something else for images (e.g. embedding a new into my app and assigning the URI to that).
I would be thankful for any suggestion you might have on how to solve this problem. The code I use to download the file currently is shown below.
Thank you for your time.
var filePath = cordova.file.externalDataDirectory; // Note: documentsDirectory is set to "" by Cordova, so I cannot use that
var fileName = "someFileName.pdf";
var mime = "application/pdf";
var dataBlob = /* some blob containing the binary data for a PDF */
function writeFile(fileEntry, dataBlob) {
// Create a FileWriter object for our FileEntry.
// This code is taken directly from the cordova-plugin-file documentation
fileEntry.createWriter(function (fileWriter) {
fileWriter.onwriteend = function() {
console.log("Successful file write...");
readFile(fileEntry);
};
fileWriter.onerror = function (e) {
console.log("Failed file write: " + e.toString());
};
fileWriter.write(dataBlob);
});
}
window.resolveLocalFileSystemURL(
filePath,
function onResolveSuccess (dirEntry) {
dirEntry.getFile(
fileName,
{ create: true },
function onGetFileSuccess (file) (
writeFile(file, dataBlob);
// At this point, the file has been downloaded successfully
window.open(file.toURL(), "_system"); // This line does nothing, and I don't understand why.
}
);
}
);
I managed to solve the problem.
As per the documentation of the file-opener2 plugin, you need to also add the androidx-adapter plugin to correct for the outdated (android 4) packages. With the plugins file, file-opener2 and androidx-adapter installed, the complete code is the following:
var filePath = cordova.file.externalDataDirectory; // Note: documentsDirectory is set to "" by Cordova, so I cannot use that
var fileName = "someFileName.pdf";
var mime = "application/pdf";
var dataBlob = /* some blob containing the binary data for a PDF */
function writeFile(fileEntry, dataBlob) {
// Create a FileWriter object for our FileEntry.
// This code is taken directly from the cordova-plugin-file documentation
fileEntry.createWriter(function (fileWriter) {
fileWriter.onwriteend = function() {
console.log("Successful file write...");
readFile(fileEntry);
};
fileWriter.onerror = function (e) {
console.log("Failed file write: " + e.toString());
};
fileWriter.write(dataBlob);
});
}
window.resolveLocalFileSystemURL(
filePath,
function onResolveSuccess (dirEntry) {
dirEntry.getFile(
fileName,
{ create: true },
function onGetFileSuccess (file) (
writeFile(file, dataBlob);
// At this point, the file has been downloaded successfully
cordova.plugins.fileOpener2.open(
filepath + filename,
mime,
{
error : function(){ },
success : function(){ }
}
);
}
);
}
);
I am using the cordova-plugin-camera in order to access the camera in my hybrid app.
I need to capture the photo and send it for upload as base64.
When capturing a photo, I can specify the following:
destinationType: Camera.DestinationType.DATA_URL
which returns an image encoded as base64. However, DATA_URL can be very memory intensive and cause app crashes or out of memory errors (as mentioned in the plugin's documentation). Because of this, my app crashes on weaker devices, so using DATA_URL is a no-go. Using the default FILE_URI should solve this problem, but my backend is configured to only accept base64 encoded images.
My question is, is there a way to convert an image from FILE_URI to DATA_URL base64 encoding?
You could download the picture from the FILE_URI and then you'd have to render the image in a canvas and then from the canvas, get the base64 using the canvas.toDataURL method.
To convert your FILE_URI to base64 data which is DATA_URL, you can do as below:
getFileContentAsBase64(FILE_URI, callback) {
window.resolveLocalFileSystemURL(path, gotFile, fail);
function fail(e) {
alert('Cannot found requested file');
}
function gotFile(fileEntry) {
fileEntry.file(function (file) {
var reader = new FileReader();
reader.onloadend = function (e) {
var content = this.result;
callback(content);
};
// The most important point, use the readAsDatURL Method from the file plugin
reader.readAsDataURL(file);
});
}
}
This returns a promise, to get your base64 data :
this.getFileContentAsBase64(thisResult.filename, (base64Image) => {
// Then you'll be able to handle the image file as base64
});
PS : FILE_URI should look like file:///storage/0/android...
I am trying to pass an image created by Cordova's camera plugin to Amazon Web Service's S3.
In the past, I have used the HTML File API to create my S3 params, and been able to pass the file object. I can't directly link to how you do this, but there is an example on this page under the section 'example uses the HTML5 File API to upload a file on disk to S3'.
But this file has not been inserted by an input element, so I can't access anything like files[0] - the file is returned by Cordova either as a base64 or an available file location. So I am trying to figure out how I would replicate that action using Cordova information.
The solution I have worked off of is found here which results in the code below:
function onSuccess(imageData) {
window.resolveLocalFileSystemURL(imageData, function(fileEntry) {
fileEntry.file(function(file) {
var reader = new FileReader();
reader.onloadend = function(evt) {
var theBody = btoa(evt.target._result);
var 3_params = {
Bucket: 'the-Bucket',
Key: 'the-Key',
ContentType: file.type,
Body: theBody
}
...other S3 SDK code...
};
reader.readAsDataURL(file);
}
}
}
This process works, but:
I have to take _result (which is a base64) through btoa which means...
It is pretty slow.
It results in larger file sizes than I should need
It also requires that I use getObject instead of getSignedURL
When I get each object, I then have to put it through atob which means...
If I have several objects to get things go VERY slow
What I would like to do is send the actual file object instead of messing with this base64. Alternatively, I would take a better/smarter/faster way to handle the base64 to S3 process.
I was able to use the 'JavaScript Canvas to Blob' polyfill to create a blob from my Base64, and then send the returned Blob on to S3. Because it is now a blob instead of a coded string, I can use getSignedURL in the s3 APK to reference the image.
The only change from above is:
var theBody = btoa(evt.target._result);
Becomes
var theBody = window.dataURLtoBlob(evt.target._result);
https://github.com/blueimp/JavaScript-Canvas-to-Blob
For those who find this, there is a better way since the new features introduced in XMLHttpRequest and now we can use readers and Blob as a source.
The initial code now could be written using FileReader.readAsArrayBuffer method and passing Blob object as the body source:
function onSuccess(imageData) {
window.resolveLocalFileSystemURL(imageData, function(fileEntry) {
fileEntry.file(function(file) {
var reader = new FileReader();
reader.onloadend = function(evt) {
var blob = new Blob([new Uint8Array(this.result)], { type: file.type });
var 3_params = {
Bucket: 'the-Bucket',
Key: 'the-Key',
ContentType: file.type,
Body: blob
}
...other S3 SDK code...
};
reader.readAsArrayBuffer(file);
}
}
}
More info can be found at Cordova Blog - Transition off of cordova-plugin-file-transfer
I am using the Cordova Filechooser plugin to select files from my Android device. The plugin returns a content:// URI (e.g. content://com.android.providers.media.documents/document/image%3A15756). I am making a call to resolveLocalFileSystemURI in order to be able to resolve the content URL and draw the image on a canvas. However for some reason the URI isn't being resolved properly.
E.g. The returned entry's fullPath is /com.android.providers.media.documents/document/image%3A15756
for the content URI content://com.android.providers.media.documents/document/image%3A15756
Any ideas? My code is as follows:
window.resolveLocalFileSystemURI(_this.target_image, function (fileEntry) {
var img = new Image();
alert(fileEntry.fullPath);
img.src = URL.createObjectURL(fileEntry.fullPath);
img.onload = function() {
combiner_context.drawImage(img, 0, 0);
combiner_context.putImage(0, img.height, _that.editor_img);
};
}, function () {
alert('Could not load selected file. Please try again.');
});
I was able to convert from a "content://" URI to a "file://" URI using this plugin: https://www.npmjs.com/package/cordova-plugin-filepath.
After obtaining the "file://" URI, I'm then able to use Cordova's resolveLocalFileSystemURL() function.
Hope this helps.
if (fileUri.startsWith("content://")) {
//We have a native file path (usually returned when a user gets a file from their Android gallery)
//Let's convert to a fileUri that we can consume properly
window.FilePath.resolveNativePath(fileUri, function(localFileUri) {
window.resolveLocalFileSystemURL("file://" + localFileUri, function(fileEntry) {/*Do Something*/});
});
}
I am updating an app from phonegap 2.* to cordova 3.4
Things are running smooth now, only the file download is not working.
I need to download a file from the internet (host edited) and store it as an JSON file, to have the contents processed later on.
The download is working fine, the file will be shown in the filesystem, but the FileReader does not fire the onloadend event.
I have tried a few things like onprogress or onerror events, also file.toURI and FileReader.readAsDataURL - nothing worked. Anybody any ideas?
Notes:
app.log can be seen as an alias for console.log
print_r is defined in another file, working fine
The downloaded file is just a few kB, shouldn't be a performance issue
Running on iOS hardware
Full code (extracted and shortened):
var fileTransfer = new FileTransfer();
var loadingStatus = 0;
fileTransfer.onprogress = function (progressEvent) {
// if we have the complete length we can calculate the percentage, otherwise just count up
if (progressEvent.lengthComputable) {
loadingStatus = Math.floor(progressEvent.loaded / progressEvent.total * 100);
} else {
loadingStatus++;
}
app.log('Transfer Progress: ' + loadingStatus);
};
fileTransfer.download(
encodeURI('http://www.example.com/export'),
'cdvfile://localhost/persistent/import.json',
function (file) {
var FileReader = new FileReader();
FileReader.onloadend = function (evt) {
app.log('Filereader onloadend');
app.log(evt);
};
FileReader.readAsText(file);
},
function (error) {
// FileTransfer failed
app.log("FileTransfer Error: " + print_r(error));
}
);
The File API has been updated. See this post: https://groups.google.com/forum/#!topic/phonegap/GKoTOSqD2kc
file.file(function(e) {
console.log("called the file func on the file ob");
var reader = new FileReader();
reader.onloadend = function(evt) {
app.log('onloadend');
app.log(evt.target.result);
};
reader.readAsText(e);
});
Cant verify this at the moment but since 3.0, Cordova implements device-level APIs as plugins. Use the CLI's plugin command, described in The Command-line Interface, to add or remove this feature for a project:
$ cordova plugin add https://git-wip-us.apache.org/repos/asf/cordova-plugin-file.git
$ cordova plugin rm org.apache.cordova.core.file
Did you add the plugin to your project?