Saturday, August 13, 2016

Android Record Video in Background Without Preview

Hello,

Recently in one of my project we have a requirement to record video in background with just one button click. No need to display preview or no need of user action, like tap record button. And also stop video recording with just one button click.

In this blog I am going to explain how to do this.

So I have used Android Media Recorder for the same. Since it was not working without preview I added SurfaceView with hidden mode by setting height to 1 so nobody can see it.

First of all lets add some global variables.

View webview;
private Camera mCamera;
private SurfaceView mPreview;
private MediaRecorder mMediaRecorder;
File outputFile;
public static final int MEDIA_TYPE_IMAGE = 1;

public static final int MEDIA_TYPE_VIDEO = 2;

Since we are adding surface view dynamically, add following class to your activity.

    public class CameraPreview extends SurfaceView implements SurfaceHolder.Callback {
        private SurfaceHolder mHolder;
        private Camera mCamera;

        public CameraPreview(Context context, Camera camera) {
            super(context);
            mCamera = camera;

            // Install a SurfaceHolder.Callback so we get notified when the
            // underlying surface is created and destroyed.
            mHolder = getHolder();
            mHolder.addCallback(this);
            // deprecated setting, but required on Android versions prior to 3.0
            mHolder.setType(SurfaceHolder.SURFACE_TYPE_PUSH_BUFFERS);
        }

        public void surfaceCreated(SurfaceHolder holder) {
            // The Surface has been created, now tell the camera where to draw the preview.
            try {
                mCamera.setPreviewDisplay(holder);
                mCamera.startPreview();
            } catch (IOException e) {
                Log.d(TAG, "Error setting camera preview: " + e.getMessage());
            }
        }

        public void surfaceDestroyed(SurfaceHolder holder) {
            // empty. Take care of releasing the Camera preview in your activity.
        }

        public void surfaceChanged(SurfaceHolder holder, int format, int w, int h) {
            // If your preview can change or rotate, take care of those events here.
            // Make sure to stop the preview before resizing or reformatting it.

            if (mHolder.getSurface() == null){
              // preview surface does not exist
              return;
            }

            // stop preview before making changes
            try {
                mCamera.stopPreview();
            } catch (Exception e){
              // ignore: tried to stop a non-existent preview
            }

            // set preview size and make any resize, rotate or
            // reformatting changes here

            // start preview with new settings
            try {
                mCamera.setPreviewDisplay(mHolder);
                mCamera.startPreview();

            } catch (Exception e){
                Log.d(TAG, "Error starting camera preview: " + e.getMessage());
            }
        }
    }  

Now first of all lets prepare everything for for recording.

Button startRecording = (Button) findViewById(R.id.startRecording);
startRecording.setOnClickListener( new OnClickListener() {
@Override
public void onClick(View v) {
               initVideoReocrding();
}
});


 public void initVideoReocrding(){
    // Create an instance of Camera
        appView = (Button) findViewById(R.id.mainView)
        mCamera = getCameraInstance();

        // Create our Preview view and set it as the content of our activity.
        mPreview = new CameraPreview(this, mCamera);
        mPreview.setId(107);
        
        android.widget.FrameLayout.LayoutParams params = new android.widget.FrameLayout.LayoutParams(1, 1);
        mPreview.setLayoutParams(params);
        ((ViewGroup) appView.getView().getParent().getParent()).addView(mPreview); 
    }

Since we are adding surface view dynamically we have to call start recording function after sometime.

final Handler handler = new Handler();
handler.postDelayed(new Runnable() {
  @Override
  public void run() {
    startVideoRecording();
  }
}, 1000);

public void startVideoRecording(){
if(prepareVideoRecorder()){
Log.v("MediaRecorder","MediaRecorder is ready");
mMediaRecorder.start();
}
}

Here are the additional function we need for this.

public static Camera getCameraInstance(){
Camera c = null;
try {
c = Camera.open(); // attempt to get a Camera instance
}
catch (Exception e){
// Camera is not available (in use or does not exist)
}
return c; // returns null if camera is unavailable
}

private void releaseMediaRecorder(){
if (mMediaRecorder != null) {
mMediaRecorder.reset();   // clear recorder configuration
mMediaRecorder.release(); // release the recorder object
mMediaRecorder = null;
mCamera.lock();           // lock camera for later use
}
}

private void releaseCamera(){
if (mCamera != null){
mCamera.release();        // release the camera for other applications
mCamera = null;
}
}

/** Create a file Uri for saving an image or video */
private static Uri getOutputMediaFileUri(int type){
 return Uri.fromFile(getOutputMediaFile(type));
}

/** Create a File for saving an image or video */
private static File getOutputMediaFile(int type){
// To be safe, you should check that the SDCard is mounted
// using Environment.getExternalStorageState() before doing this.

File mediaStorageDir = new File(Environment.getExternalStoragePublicDirectory(
 Environment.DIRECTORY_PICTURES), "MYAPP");
// This location works best if you want the created images to be shared
// between applications and persist after your app has been uninstalled.

// Create the storage directory if it does not exist
if (! mediaStorageDir.exists()){
if (! mediaStorageDir.mkdirs()){
Log.d("MYAPP", "failed to create directory");
return null;
}
}

// Create a media file name
String timeStamp = new SimpleDateFormat("yyyyMMdd_HHmmss").format(new Date());
File mediaFile;
if (type == MEDIA_TYPE_IMAGE){
mediaFile = new File(mediaStorageDir.getPath() + File.separator +
"IMG_"+ timeStamp + ".jpg");
} else if(type == MEDIA_TYPE_VIDEO) {
mediaFile = new File(mediaStorageDir.getPath() + File.separator +
"VID_"+ timeStamp + ".mp4");
} else {
return null;
}

return mediaFile;
}

private boolean prepareVideoRecorder(){

Camera temp = getCameraInstance();
if(temp != null){
mCamera = temp;
}
mMediaRecorder = new MediaRecorder();

// Step 1: Unlock and set camera to MediaRecorder
mCamera.unlock();
mMediaRecorder.setCamera(mCamera);

// Step 2: Set sources
mMediaRecorder.setAudioSource(MediaRecorder.AudioSource.CAMCORDER);
mMediaRecorder.setVideoSource(MediaRecorder.VideoSource.CAMERA);

// Step 3: Set a CamcorderProfile (requires API Level 8 or higher)
mMediaRecorder.setProfile(CamcorderProfile.get(CamcorderProfile.QUALITY_HIGH));

// Step 4: Set output file
outputFile = getOutputMediaFile(MEDIA_TYPE_VIDEO);
mMediaRecorder.setOutputFile(outputFile.toString());

// Step 5: Set the preview output
mMediaRecorder.setPreviewDisplay(mPreview.getHolder().getSurface());
// Step 6: Prepare configured MediaRecorder
try {
mMediaRecorder.prepare();
} catch (IllegalStateException e) {
Log.d(TAG, "IllegalStateException preparing MediaRecorder: " + e.getMessage());
releaseMediaRecorder();
return false;
} catch (IOException e) {
Log.d(TAG, "IOException preparing MediaRecorder: " + e.getMessage());
releaseMediaRecorder();
return false;
}
return true;
}


And following is the function to stop recording.

public void stopVideoRecording(){
    mMediaRecorder.stop();  // stop the recording
        releaseMediaRecorder(); // release the MediaRecorder object
        mCamera.lock();  
        mCamera.stopPreview();
        Log.v("OutputFile", outputFile.getAbsolutePath());
        
        MediaScannerConnection.scanFile(getApplicationContext(), new String[]{outputFile.getAbsolutePath()}, null, null);
        View previewView = ((ViewGroup) appView.getView().getParent().getParent()).findViewById(107);
        ((ViewGroup) appView.getView().getParent().getParent()).removeView(previewView);
    }
    




Cordova Camera Plugin Select Video Returns null FILE URI

Hello,

Recently in one of my project we have a requirement to select video from Gallery and import it to Cordova application.

For that I used following code.

        navigator.camera.getPicture(this.onVideoSuccess, function(){
            console.log('Error in Video Processing);
        }, {
            destinationType: Camera.DestinationType.FILE_URI,
            sourceType: navigator.camera.PictureSourceType.PHOTOLIBRARY,
            mediaType:Camera.MediaType.VIDEO,
            limit: 1
        });

As you can see in above code we are selecting media type to video. Following is our callback function.

    onVideoSuccess: function(videoURI){
        console.log(videoURI);
    }

Now the problem was it was always returns null URI for any video I select. So I decided to dig into plugin code and found issue in CameraLauncher.java. Following is the function.


private void processResultFromGallery(int destType, Intent intent) {
 Uri uri = intent.getData();
        if (uri == null) {
            if (croppedUri != null) {
                uri = croppedUri;
            } else {
                this.failPicture("null data from photo library");
                return;
            }
        }
        .........
        .........
Here we have issue in following line.

        String fileLocation = FileHelper.getRealPath(uri, this.cordova);

This always returns null, however we were getting URI. So I added following code. 

if (this.mediaType != PICTURE) {
         if (fileLocation == null) {
         Cursor cursor = null;
          try
            String[] proj = { MediaStore.Images.Media.DATA };
            cursor = this.webView.getContext().getContentResolver().query(uri,  proj, null, null, null);
            int column_index = cursor.getColumnIndexOrThrow(MediaStore.Images.Media.DATA);
            cursor.moveToFirst();
            fileLocation = cursor.getString(column_index);
          } finally {
            if (cursor != null) {
              cursor.close();
            }
          }
         }
            this.callbackContext.success(fileLocation);
        }

and it worked.  Now it returned correct URI.

Hope this helps you.

Cordova / Sencha Touch Application Import and Show Image form Native Phone Gallery

Hello,

Recently in one of my cordova application we have requirement to select images from Phone gallery and display it in list view.  For that we have to allow use to select multiple images from gallery. Here in this blog I am going to explain how to do this.

First of all we will need following plugin installed.

Cordova Image Picture.

https://github.com/wymsee/cordova-imagePicker

If you install this plugin with following command, some how it does not work.

cordova plugin add cordova-plugin-image-picker

So instead of installing it from the command install it with direct git URL.

cordova plugin add "https://github.com/wymsee/cordova-imagePicker"

Along with above plugins, we will also need Cordova File Plugin and Cordova File Transfer Plugin.

Install it with following commands.

cordova plugin add cordova-plugin-file
cordova plugin add cordova-plugin-file-transfer

Now first of all we have to open image gallery with this image picker plugin to choose images.

Add following code.

window.imagePicker.getPictures(
    function(results) {
        for (var i = 0; i < results.length; i++) {
            console.log('Image URI: ' + results[i]);
        }
    }, function (error) {
        console.log('Error: ' + error);
    }
);

Please note that this plugin copy the images it's location temporary location to app directory and returns it's path which is a temporary path so we have to get absolute path to display images. For this modify above code as follow.

window.imagePicker.getPictures(
function(results) {
for (var i = 0; i < results.length; i++) {
console.log(results[i]);

var location= results[i];

window.resolveLocalFileSystemURL(location, function(oFile) {
oFile.file(function(readyFile) {
var reader= new FileReader();
reader.onloadend= function(evt) {
Ext.getStore('ImageGallery').add({
fileURI: evt.target.result
});
Ext.getStore('ImageGallery').sync();
};
reader.readAsDataURL(readyFile);
});
}, function(err){
console.log('### ERR: filesystem.directoryUp() - ' + (JSON.stringify(err)));
});
}

}, function (error) {
console.log('Error: ' + error);
}
);

As you can see in above code, using the temporary location we are getting absolute path of images and adding it to Sencha Touch stores so we use it to display in Sencha Touch data view.

Following is the Store and Model definition.

Ext.define('MyApp.model.ImageGallery',  {
    extend: 'Ext.data.Model',
    config:
    {
        idProperty: 'id',
        fields:
            [

                { name: "id", type: 'int' },
                { name: 'fileURI', type: 'string'}
            ]
    }
});

Ext.define('MyApp.store.ImageGallery', {
    extend: 'Ext.data.Store',
    config:
    {
        autoLoad: true,
        model: 'MyApp.model.ImageGallery',


        proxy:
        {
            type: 'localstorage',
            id: 'image-gallery'
        }
    }
});

And following is the dataview.

 {
xtype: 'dataview',
height: '100%',
width: '100%',
layout: {
type: 'fit'
},
inline: {
wrap: true
},
style: 'background-color:#fefefe',
margin: '6% 6% 6% 6%',
itemTpl: [
 '<div style="width: 140px;height:140px;overflow: hidden">'+
 '<img id="gallery_{id}" style="position: relative;left: 15px;top: -115px;z-index: 1" src="{fileURI}" width="100" height="100"/>'+
 '</div>'

],
store: 'ImageGallery',
itemId: 'imageGalleryDataView',
id: 'imageGalleryDataView',
mode: 'MULTI',
selectedCls: ''
}

Ultimate output will look like this.








Paypal Create Recurring Payment Profile From Laravel

Recently one of our Laravel project we have subscriptions management with recurring payment options with Paypal. It took a while for me to integrate it with my Laravel project so here in this blog I am going to explain how to do this. Please note example given here is using sandbox account.

First of all we need Paypal Merchant SDK.

You can find it here from this link https://github.com/paypal/merchant-sdk-php

Import it with composer. Add following line to your composer.json file in require section and run composer update command.

"paypal/merchant-sdk-php":"3.8.*"

Once the composer is updated your local vendor folder will have paypal directory with all the classes. Please note fort this SDK your will need PHP version 6 and on words.

Now first of all we will need a Paypal token to initiate recurring profile creation.

Go to your Laravel controller and add following classes of Paypal merchant SDK.

use PayPal\Service\PayPalAPIInterfaceServiceService;
use PayPal\EBLBaseComponents\PaymentDetailsType;
use PayPal\CoreComponentTypes\BasicAmountType;
use PayPal\EBLBaseComponents\SetExpressCheckoutRequestDetailsType;
use PayPal\EBLBaseComponents\BillingAgreementDetailsType;
use PayPal\PayPalAPI\SetExpressCheckoutRequestType;
use PayPal\PayPalAPI\SetExpressCheckoutReq;
use PayPal\EBLBaseComponents\RecurringPaymentsProfileDetailsType;
use PayPal\EBLBaseComponents\BillingPeriodDetailsType;
use PayPal\EBLBaseComponents\ScheduleDetailsType;
use PayPal\EBLBaseComponents\CreateRecurringPaymentsProfileRequestDetailsType;
use PayPal\PayPalAPI\CreateRecurringPaymentsProfileRequestType;
use PayPal\PayPalAPI\CreateRecurringPaymentsProfileReq;


Now lets first get token. Add following code to your controller function.

        $config = array (
            'mode' => 'sandbox' ,
            'acct1.UserName' => 'yourusername,
            'acct1.Password' => 'yourpassword',
            'acct1.Signature' => 'yourapisignature'
        );
        $paypalService = new PayPalAPIInterfaceServiceService($config);
        $paymentDetails= new PaymentDetailsType();

        $orderTotal = new BasicAmountType();
        $orderTotal->currencyID = 'USD';
        $orderTotal->value = '10.00';//your own value

        $paymentDetails->OrderTotal = $orderTotal;
        $paymentDetails->PaymentAction = 'Sale';

        $setECReqDetails = new SetExpressCheckoutRequestDetailsType();
        $setECReqDetails->PaymentDetails[0] = $paymentDetails;
        $setECReqDetails->CancelURL = 'http://yourcancelurl/cancel-paypal-payment';
        $setECReqDetails->ReturnURL = 'http://yoursuccessurl/qt/success-paypal-payment';

        $billingAgreementDetails = new BillingAgreementDetailsType('RecurringPayments');
        $billingAgreementDetails->BillingAgreementDescription = 'Recurring Billing';
        $setECReqDetails->BillingAgreementDetails = array($billingAgreementDetails);

        $setECReqType = new SetExpressCheckoutRequestType();
        $setECReqType->Version = '104.0';
        $setECReqType->SetExpressCheckoutRequestDetails = $setECReqDetails;

        $setECReq = new SetExpressCheckoutReq();
        $setECReq->SetExpressCheckoutRequest = $setECReqType;

        $setECResponse = $paypalService->SetExpressCheckout($setECReq);

        if($setECResponse->Ack == 'Success'){
            $token = $setECResponse->Token;
            return Redirect::to('https://www.sandbox.paypal.com/cgi-bin/webscr?cmd=_express-checkout&token='.$token);
        }else{
            return Redirect::to('error')->with('message', 'There is a problem in payment processing.');
        }

As you can see in above code. As soon as we got token we are sending user to Paypal for authorization. Once it is authorized from Paypal you will get payer id and you will return back to your website on the URL specified in above code.

Now we get Payer id we have to create recurring profile.

        $profileDetails = new RecurringPaymentsProfileDetailsType();
        $profileDetails->BillingStartDate = date(DATE_ISO8601, strtotime(date('Y-m-d').' +1 day'));
        //Since date start should be greater than current date.

        $paymentBillingPeriod = new BillingPeriodDetailsType();
        $paymentBillingPeriod->BillingFrequency = 1;//your own value
        $paymentBillingPeriod->BillingPeriod = "Month";//your own value
       

        $paymentBillingPeriod->Amount = new BasicAmountType("USD", "10.00");//your own value

        $scheduleDetails = new ScheduleDetailsType();
        $scheduleDetails->Description = "Recurring Billing";
        $scheduleDetails->PaymentPeriod = $paymentBillingPeriod;

        $createRPProfileRequestDetails = new CreateRecurringPaymentsProfileRequestDetailsType();
        $createRPProfileRequestDetails->Token = $token;

        $createRPProfileRequestDetails->ScheduleDetails = $scheduleDetails;
        $createRPProfileRequestDetails->RecurringPaymentsProfileDetails = $profileDetails;

        $createRPProfileRequest = new CreateRecurringPaymentsProfileRequestType();
        $createRPProfileRequest->CreateRecurringPaymentsProfileRequestDetails = $createRPProfileRequestDetails;

        $createRPProfileReq = new CreateRecurringPaymentsProfileReq();
        $createRPProfileReq->CreateRecurringPaymentsProfileRequest = $createRPProfileRequest;

        $config = array (
            'mode' => 'sandbox' ,
            'acct1.UserName' => 'yourusername,
            'acct1.Password' => 'yourpassword',
            'acct1.Signature' => 'yourapisignature'
        );
        $paypalService = new PayPalAPIInterfaceServiceService($config);
        $createRPProfileResponse = $paypalService->CreateRecurringPaymentsProfile($createRPProfileReq);

Now we have to check if the recurring profile successfully created.

if($createRPProfileResponse->Ack == 'Success'){
            $profileId = $createRPProfileResponse->CreateRecurringPaymentsProfileResponseDetails->ProfileID;
            //your own logic to save details in database.

}else{
            return Redirect::to('error')->with('message', 'There is a problem in payment processing.');
        }

That's it and your recurring payment profile is created.

Hope this helps you.

JavaScript parseInt('08') Returns Zero

Recently in one of our product we faced strange issue related to date display.  We were passing date components to display like this.

1st August 2016

Everything was working fine, till we faced the issue since start of Month of August. Instead of displaying correct date it was displaying 1 year old date or any random date.

It took sometime for me to figure out issue. Issue was number system. When we parse as following

parseInt("08")

In some browser or some android phones, it is considered as octal number and octal number system is used to parse. So "08" is "0" in octal so it gives wrong number.

So to fix this issue you can either use radix parameter in parseInt

parseInt("08", 10);

Here radix 10 represent decimal number system so "08" is parsed as "8" Other solution is to use Number function to parse the string.

Number("08");

And it gives you correct result. Octal system is deprecated in most of the browser now but still some old browser and some android phones are still using it so we have to to use above methods to avoid any issue.

Hope this helps you.