Browser fallback for running PhoneGap camera plugin on localhost
20 Mar 2014The cool thing about PhoneGap is working in an environment that you are used to. You can work with your usual text editor, the command line and the browser to preview, test and debug your app running on localhost.
As soon as you start using PhoneGap plugins, like the camera plugin, you are not able to run your code in the browser anymore. Compiling the app, running it in an emulator or an actual device and reading logs in the console takes time and debugging is hard.
Wouldn’t it be awesome to make the camera plugin work in the browser? So let’s take a look what the camera plugin actually does.
Most of the time I set the destinationType
to DATA_URL
. That returns an image as base64-encoded string. We therefore
have to find a solution for the browser to upload an image from file and get
it back as a base64-encoded string.
To make a long story short here is the code. Read more about FileReader and readAsDataURL.
// create file input without appending to DOM
var fileInput = document.createElement('input');
fileInput.setAttribute('type', 'file');
fileInput.onchange = function() {
var file = fileInput.files[0];
var reader = new FileReader();
reader.readAsDataURL(file);
reader.onloadend = function () {
// strip beginning from string
var encodedData = reader.result.replace(/data:image\/jpeg;base64,/, '');
};
};
fileInput.click();
In order to use this code in production I wrote an AngularJS service which first checks whether I’m in the browser or on the actual device. Depending on the environment it either runs the code from above or starts the Cordova camera plugin.
angular.module('app.services')
.factory('camera', ['$rootScope', '$q', 'env', function($rootScope, $q, env) {
return {
getPicture: function(options) {
// init $q
var deferred = $q.defer();
if (env.browser) {
// create file input without appending to DOM
var fileInput = document.createElement('input');
fileInput.setAttribute('type', 'file');
fileInput.onchange = function() {
var file = fileInput.files[0];
var reader = new FileReader();
reader.readAsDataURL(file);
reader.onloadend = function () {
$rootScope.$apply(function() {
// strip beginning from string
var encodedData = reader.result.replace(/data:image\/jpeg;base64,/, '');
deferred.resolve(encodedData);
});
};
};
fileInput.click();
} else {
// set some default options
var defaultOptions = {
quality: 75,
destinationType: Camera.DestinationType.DATA_URL,
allowEdit: true,
targetWidth: 75,
targetHeight: 75
};
// allow overriding the default options
options = angular.extend(defaultOptions, options);
// success callback
var success = function(imageData) {
$rootScope.$apply(function() {
deferred.resolve(imageData);
});
};
// fail callback
var fail = function(message) {
$rootScope.$apply(function() {
deferred.reject(message);
});
};
// open camera via cordova
navigator.camera.getPicture(success, fail, options);
}
// return a promise
return deferred.promise;
}
};
}]);
Inject this service into your controller and call it.
angular.module('app.controllers').controller('PhotoCtrl',
['$scope', 'camera', function($scope, camera) {
// can be a button click or anything else
$scope.takePicture = function() {
camera.getPicture()
.then(function(imageData) {
// imageData is your base64-encoded image
// update some ng-src directive
$scope.picSrc = "data:image/jpeg;base64," + imageData;
})
.catch(function(err) {
console.log(err);
});
};
}]);
You are now able to run your app in the browser on your local machine. As soon
as you call takePicture
the normal file input dialog pops up and you can
chose an image from file. That image is converted into its base64 representation
and returned from our AngularJS service. Running this code on the device starts
the camera app as expected.
You might wonder why I removed data:image/jpeg;base64,
within the service and
prepended it again in the controller. In a real application you’d want to save
the image somewhere, for example a database like PouchDB.
Attachments in PouchDB are stored as Blobs. Converting a base64-encoded String
into a blob won’t work with data:image/jpeg;base64,
at the beginning. You need
the raw data.
Before you ask here are two helper functions to convert Base64 to Blob and vice versa.
angular.module('app.services').factory('utils',
['$rootScope', '$q', function($rootScope, $q) {
return {
base64ToBlob: function(data) {
// https://github.com/daleharvey/pouchdb/blob/master/tests/test.attachments.js#L523
var decodedData = PouchDB.utils.atob(data);
var fixedBinary = PouchDB.utils.fixBinary(decodedData);
var blob = PouchDB.utils.createBlob([fixedBinary], {type: 'image/jpeg'});
return blob;
},
blobToBase64: function(blob) {
var deferred = $q.defer();
var reader = new FileReader();
reader.readAsBinaryString(blob);
reader.onloadend = function() {
var res = this.result;
$rootScope.$apply(function() {
deferred.resolve(PouchDB.utils.btoa(res));
});
};
return deferred.promise;
}
};
}]);
Now have fun with the camera plugin.
Tweet comments powered by Disqus