diff --git a/README.md b/README.md index e2acdd40..04b5dbc1 100644 --- a/README.md +++ b/README.md @@ -21,9 +21,9 @@ description: Capture audio, video, and images. # under the License. --> -|AppVeyor|Travis CI| -|:-:|:-:| -|[![Build status](https://ci.appveyor.com/api/projects/status/github/apache/cordova-plugin-media-capture?branch=master)](https://ci.appveyor.com/project/ApacheSoftwareFoundation/cordova-plugin-media-capture)|[![Build Status](https://travis-ci.org/apache/cordova-plugin-media-capture.svg?branch=master)](https://travis-ci.org/apache/cordova-plugin-media-capture)| +| AppVeyor | Travis CI | +| :------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------: | :-------------------------------------------------------------------------------------------------------------------------------------------------------: | +| [![Build status](https://ci.appveyor.com/api/projects/status/github/apache/cordova-plugin-media-capture?branch=master)](https://ci.appveyor.com/project/ApacheSoftwareFoundation/cordova-plugin-media-capture) | [![Build Status](https://travis-ci.org/apache/cordova-plugin-media-capture.svg?branch=master)](https://travis-ci.org/apache/cordova-plugin-media-capture) | # cordova-plugin-media-capture @@ -332,10 +332,12 @@ capturing a video clip, the `CaptureErrorCB` callback executes with a - __limit__: The maximum number of images the user can capture in a single capture operation. The value must be greater than or equal to 1 (defaults to 1). +- __cameraDirection__: Pick camera direction. Accepted values are 1 (for front camera) and 0 (for back camera). Or you can use the constants (window.MediaCapture.CAMERA_FRONT or window.MediaCapture.CAMERA_BACK) + ### Example // limit capture operation to 3 images - var options = { limit: 3 }; + var options = { limit: 3, cameraDirection: window.MediaCapture.CAMERA_FRONT }; navigator.device.capture.captureImage(captureSuccess, captureError, options); @@ -354,10 +356,12 @@ capturing a video clip, the `CaptureErrorCB` callback executes with a - __duration__: The maximum duration of a video clip, in seconds. +- __cameraDirection__: Pick camera direction. Accepted values are 1 (for front camera) and 0 (for back camera). Or you can use the constants (window.MediaCapture.CAMERA_FRONT or window.MediaCapture.CAMERA_BACK) + ### Example // limit capture operation to 3 video clips - var options = { limit: 3 }; + var options = { limit: 3, cameraDirection: window.MediaCapture.CAMERA_FRONT }; navigator.device.capture.captureVideo(captureSuccess, captureError, options); diff --git a/plugin.xml b/plugin.xml index e50e8e59..30014d91 100644 --- a/plugin.xml +++ b/plugin.xml @@ -35,6 +35,10 @@ xmlns:android="http://schemas.android.com/apk/res/android" + + + + diff --git a/src/android/Capture.java b/src/android/Capture.java index 317d0ebc..53bb4bc3 100644 --- a/src/android/Capture.java +++ b/src/android/Capture.java @@ -63,48 +63,55 @@ public class Capture extends CordovaPlugin { private static final String VIDEO_3GPP = "video/3gpp"; private static final String VIDEO_MP4 = "video/mp4"; private static final String AUDIO_3GPP = "audio/3gpp"; - private static final String[] AUDIO_TYPES = new String[] {"audio/3gpp", "audio/aac", "audio/amr", "audio/wav"}; + private static final String[] AUDIO_TYPES = new String[] { "audio/3gpp", "audio/aac", "audio/amr", "audio/wav" }; private static final String IMAGE_JPEG = "image/jpeg"; - private static final int CAPTURE_AUDIO = 0; // Constant for capture audio - private static final int CAPTURE_IMAGE = 1; // Constant for capture image - private static final int CAPTURE_VIDEO = 2; // Constant for capture video + private static final int CAPTURE_AUDIO = 0; // Constant for capture audio + private static final int CAPTURE_IMAGE = 1; // Constant for capture image + private static final int CAPTURE_VIDEO = 2; // Constant for capture video private static final String LOG_TAG = "Capture"; private static final int CAPTURE_INTERNAL_ERR = 0; -// private static final int CAPTURE_APPLICATION_BUSY = 1; -// private static final int CAPTURE_INVALID_ARGUMENT = 2; + // private static final int CAPTURE_APPLICATION_BUSY = 1; + // private static final int CAPTURE_INVALID_ARGUMENT = 2; private static final int CAPTURE_NO_MEDIA_FILES = 3; private static final int CAPTURE_PERMISSION_DENIED = 4; private static final int CAPTURE_NOT_SUPPORTED = 20; - private boolean cameraPermissionInManifest; // Whether or not the CAMERA permission is declared in AndroidManifest.xml + private boolean cameraPermissionInManifest; // Whether or not the CAMERA permission is declared in + // AndroidManifest.xml private final PendingRequests pendingRequests = new PendingRequests(); - private int numPics; // Number of pictures before capture activity + private int numPics; // Number of pictures before capture activity private Uri imageUri; -// public void setContext(Context mCtx) -// { -// if (CordovaInterface.class.isInstance(mCtx)) -// cordova = (CordovaInterface) mCtx; -// else -// LOG.d(LOG_TAG, "ERROR: You must use the CordovaInterface for this to work correctly. Please implement it in your activity"); -// } + private boolean useFrontEndCamera = false; + + // public void setContext(Context mCtx) + // { + // if (CordovaInterface.class.isInstance(mCtx)) + // cordova = (CordovaInterface) mCtx; + // else + // LOG.d(LOG_TAG, "ERROR: You must use the CordovaInterface for this to work + // correctly. Please implement it in your activity"); + // } @Override protected void pluginInitialize() { super.pluginInitialize(); - // CB-10670: The CAMERA permission does not need to be requested unless it is declared - // in AndroidManifest.xml. This plugin does not declare it, but others may and so we must + // CB-10670: The CAMERA permission does not need to be requested unless it is + // declared + // in AndroidManifest.xml. This plugin does not declare it, but others may and + // so we must // check the package info to determine if the permission is present. cameraPermissionInManifest = false; try { PackageManager packageManager = this.cordova.getActivity().getPackageManager(); - String[] permissionsInPackage = packageManager.getPackageInfo(this.cordova.getActivity().getPackageName(), PackageManager.GET_PERMISSIONS).requestedPermissions; + String[] permissionsInPackage = packageManager.getPackageInfo(this.cordova.getActivity().getPackageName(), + PackageManager.GET_PERMISSIONS).requestedPermissions; if (permissionsInPackage != null) { for (String permission : permissionsInPackage) { if (permission.equals(Manifest.permission.CAMERA)) { @@ -130,16 +137,19 @@ public boolean execute(String action, JSONArray args, CallbackContext callbackCo JSONObject options = args.optJSONObject(0); + // check if use front end camera == 1 + // if that is the case then we are going to use the camera + if (options != null && options.has("cameraDirection")) { + this.useFrontEndCamera = options.getInt("cameraDirection") == 1 ? true : false; + } + if (action.equals("captureAudio")) { this.captureAudio(pendingRequests.createRequest(CAPTURE_AUDIO, options, callbackContext)); - } - else if (action.equals("captureImage")) { + } else if (action.equals("captureImage")) { this.captureImage(pendingRequests.createRequest(CAPTURE_IMAGE, options, callbackContext)); - } - else if (action.equals("captureVideo")) { + } else if (action.equals("captureVideo")) { this.captureVideo(pendingRequests.createRequest(CAPTURE_VIDEO, options, callbackContext)); - } - else { + } else { return false; } @@ -172,11 +182,9 @@ private JSONObject getFormatData(String filePath, String mimeType) throws JSONEx if (mimeType.equals(IMAGE_JPEG) || filePath.endsWith(".jpg")) { obj = getImageData(fileUrl, obj); - } - else if (Arrays.asList(AUDIO_TYPES).contains(mimeType)) { + } else if (Arrays.asList(AUDIO_TYPES).contains(mimeType)) { obj = getAudioVideoData(filePath, obj, false); - } - else if (mimeType.equals(VIDEO_3GPP) || mimeType.equals(VIDEO_MP4)) { + } else if (mimeType.equals(VIDEO_3GPP) || mimeType.equals(VIDEO_MP4)) { obj = getAudioVideoData(filePath, obj, true); } return obj; @@ -203,8 +211,8 @@ private JSONObject getImageData(Uri fileUrl, JSONObject obj) throws JSONExceptio * Get the Image specific attributes * * @param filePath path to the file - * @param obj represents the Media File Data - * @param video if true get video attributes as well + * @param obj represents the Media File Data + * @param video if true get video attributes as well * @return a JSONObject that represents the Media File Data * @throws JSONException */ @@ -225,20 +233,21 @@ private JSONObject getAudioVideoData(String filePath, JSONObject obj, boolean vi } /** - * Sets up an intent to capture audio. Result handled by onActivityResult() + * Sets up an intent to capture audio. Result handled by onActivityResult() */ private void captureAudio(Request req) { - if (!PermissionHelper.hasPermission(this, Manifest.permission.READ_EXTERNAL_STORAGE)) { - PermissionHelper.requestPermission(this, req.requestCode, Manifest.permission.READ_EXTERNAL_STORAGE); - } else { - try { - Intent intent = new Intent(android.provider.MediaStore.Audio.Media.RECORD_SOUND_ACTION); - - this.cordova.startActivityForResult((CordovaPlugin) this, intent, req.requestCode); - } catch (ActivityNotFoundException ex) { - pendingRequests.resolveWithFailure(req, createErrorObject(CAPTURE_NOT_SUPPORTED, "No Activity found to handle Audio Capture.")); - } - } + if (!PermissionHelper.hasPermission(this, Manifest.permission.READ_EXTERNAL_STORAGE)) { + PermissionHelper.requestPermission(this, req.requestCode, Manifest.permission.READ_EXTERNAL_STORAGE); + } else { + try { + Intent intent = new Intent(android.provider.MediaStore.Audio.Media.RECORD_SOUND_ACTION); + + this.cordova.startActivityForResult((CordovaPlugin) this, intent, req.requestCode); + } catch (ActivityNotFoundException ex) { + pendingRequests.resolveWithFailure(req, + createErrorObject(CAPTURE_NOT_SUPPORTED, "No Activity found to handle Audio Capture.")); + } + } } private String getTempDirectoryPath() { @@ -249,22 +258,26 @@ private String getTempDirectoryPath() { // Create the cache directory if it doesn't exist cache.mkdirs(); + + + return cache.getAbsolutePath(); } /** - * Sets up an intent to capture images. Result handled by onActivityResult() + * Sets up an intent to capture images. Result handled by onActivityResult() */ private void captureImage(Request req) { - boolean needExternalStoragePermission = - !PermissionHelper.hasPermission(this, Manifest.permission.WRITE_EXTERNAL_STORAGE); + boolean needExternalStoragePermission = !PermissionHelper.hasPermission(this, + Manifest.permission.WRITE_EXTERNAL_STORAGE); - boolean needCameraPermission = cameraPermissionInManifest && - !PermissionHelper.hasPermission(this, Manifest.permission.CAMERA); + boolean needCameraPermission = cameraPermissionInManifest + && !PermissionHelper.hasPermission(this, Manifest.permission.CAMERA); if (needExternalStoragePermission || needCameraPermission) { if (needExternalStoragePermission && needCameraPermission) { - PermissionHelper.requestPermissions(this, req.requestCode, new String[]{Manifest.permission.WRITE_EXTERNAL_STORAGE, Manifest.permission.CAMERA}); + PermissionHelper.requestPermissions(this, req.requestCode, + new String[] { Manifest.permission.WRITE_EXTERNAL_STORAGE, Manifest.permission.CAMERA }); } else if (needExternalStoragePermission) { PermissionHelper.requestPermission(this, req.requestCode, Manifest.permission.WRITE_EXTERNAL_STORAGE); } else { @@ -284,6 +297,11 @@ private void captureImage(Request req) { intent.putExtra(android.provider.MediaStore.EXTRA_OUTPUT, imageUri); + if (this.useFrontEndCamera) { + intent.putExtra("android.intent.extra.USE_FRONT_CAMERA", true); + intent.putExtra("android.intent.extras.CAMERA_FACING", 1); + } + this.cordova.startActivityForResult((CordovaPlugin) this, intent, req.requestCode); } } @@ -294,18 +312,24 @@ private static void createWritableFile(File file) throws IOException { } /** - * Sets up an intent to capture video. Result handled by onActivityResult() + * Sets up an intent to capture video. Result handled by onActivityResult() */ private void captureVideo(Request req) { - if(cameraPermissionInManifest && !PermissionHelper.hasPermission(this, Manifest.permission.CAMERA)) { + if (cameraPermissionInManifest && !PermissionHelper.hasPermission(this, Manifest.permission.CAMERA)) { PermissionHelper.requestPermission(this, req.requestCode, Manifest.permission.CAMERA); } else { Intent intent = new Intent(android.provider.MediaStore.ACTION_VIDEO_CAPTURE); - if(Build.VERSION.SDK_INT > 7){ + if (Build.VERSION.SDK_INT > 7) { intent.putExtra("android.intent.extra.durationLimit", req.duration); intent.putExtra("android.intent.extra.videoQuality", req.quality); } + + if (this.useFrontEndCamera) { + intent.putExtra("android.intent.extra.USE_FRONT_CAMERA", true); + intent.putExtra("android.intent.extras.CAMERA_FACING", 1); + } + this.cordova.startActivityForResult((CordovaPlugin) this, intent, req.requestCode); } } @@ -313,10 +337,13 @@ private void captureVideo(Request req) { /** * Called when the video view exits. * - * @param requestCode The request code originally supplied to startActivityForResult(), - * allowing you to identify who this result came from. - * @param resultCode The integer result code returned by the child activity through its setResult(). - * @param intent An Intent, which can return result data to the caller (various data can be attached to Intent "extras"). + * @param requestCode The request code originally supplied to + * startActivityForResult(), allowing you to identify who + * this result came from. + * @param resultCode The integer result code returned by the child activity + * through its setResult(). + * @param intent An Intent, which can return result data to the caller + * (various data can be attached to Intent "extras"). * @throws JSONException */ public void onActivityResult(int requestCode, int resultCode, final Intent intent) { @@ -327,16 +354,16 @@ public void onActivityResult(int requestCode, int resultCode, final Intent inten Runnable processActivityResult = new Runnable() { @Override public void run() { - switch(req.action) { - case CAPTURE_AUDIO: - onAudioActivityResult(req, intent); - break; - case CAPTURE_IMAGE: - onImageActivityResult(req); - break; - case CAPTURE_VIDEO: - onVideoActivityResult(req, intent); - break; + switch (req.action) { + case CAPTURE_AUDIO: + onAudioActivityResult(req, intent); + break; + case CAPTURE_IMAGE: + onImageActivityResult(req); + break; + case CAPTURE_VIDEO: + onVideoActivityResult(req, intent); + break; } } }; @@ -367,7 +394,6 @@ else if (resultCode == Activity.RESULT_CANCELED) { } } - public void onAudioActivityResult(Request req, Intent intent) { // Get the uri of the audio clip Uri data = intent.getData(); @@ -401,21 +427,20 @@ public void onImageActivityResult(Request req) { public void onVideoActivityResult(Request req, Intent intent) { Uri data = null; - if (intent != null){ + if (intent != null) { // Get the uri of the video clip data = intent.getData(); } - if( data == null){ + if (data == null) { File movie = new File(getTempDirectoryPath(), "Capture.avi"); data = Uri.fromFile(movie); } // create a file object from the uri - if(data == null) { + if (data == null) { pendingRequests.resolveWithFailure(req, createErrorObject(CAPTURE_NO_MEDIA_FILES, "Error: data is null")); - } - else { + } else { req.results.put(createMediaFile(data)); if (req.results.length() >= req.limit) { @@ -451,7 +476,7 @@ private JSONObject createMediaFile(Uri data) { if (pm == null) { try { Field pmf = webViewClass.getField("pluginManager"); - pm = (PluginManager)pmf.get(webView); + pm = (PluginManager) pmf.get(webView); } catch (NoSuchFieldException e) { } catch (IllegalAccessException e) { } @@ -466,8 +491,10 @@ private JSONObject createMediaFile(Uri data) { if (url != null) { obj.put("localURL", url.toString()); } - // Because of an issue with MimeTypeMap.getMimeTypeFromExtension() all .3gpp files - // are reported as video/3gpp. I'm doing this hacky check of the URI to see if it + // Because of an issue with MimeTypeMap.getMimeTypeFromExtension() all .3gpp + // files + // are reported as video/3gpp. I'm doing this hacky check of the URI to see if + // it // is stored in the audio or video content store. if (fp.getAbsoluteFile().toString().endsWith(".3gp") || fp.getAbsoluteFile().toString().endsWith(".3gpp")) { if (data.toString().contains("/audio/")) { @@ -505,17 +532,13 @@ private JSONObject createErrorObject(int code, String message) { * @return a cursor */ private Cursor queryImgDB(Uri contentStore) { - return this.cordova.getActivity().getContentResolver().query( - contentStore, - new String[] { MediaStore.Images.Media._ID }, - null, - null, - null); + return this.cordova.getActivity().getContentResolver().query(contentStore, + new String[] { MediaStore.Images.Media._ID }, null, null, null); } /** - * Used to find out if we are in a situation where the Camera Intent adds to images - * to the content store. + * Used to find out if we are in a situation where the Camera Intent adds to + * images to the content store. */ private void checkForDuplicateImage() { Uri contentStore = whichContentStore(); @@ -533,6 +556,7 @@ private void checkForDuplicateImage() { /** * Determine if we are storing the images in internal or external storage + * * @return Uri */ private Uri whichContentStore() { @@ -545,25 +569,25 @@ private Uri whichContentStore() { private void executeRequest(Request req) { switch (req.action) { - case CAPTURE_AUDIO: - this.captureAudio(req); - break; - case CAPTURE_IMAGE: - this.captureImage(req); - break; - case CAPTURE_VIDEO: - this.captureVideo(req); - break; + case CAPTURE_AUDIO: + this.captureAudio(req); + break; + case CAPTURE_IMAGE: + this.captureImage(req); + break; + case CAPTURE_VIDEO: + this.captureVideo(req); + break; } } - public void onRequestPermissionResult(int requestCode, String[] permissions, - int[] grantResults) throws JSONException { + public void onRequestPermissionResult(int requestCode, String[] permissions, int[] grantResults) + throws JSONException { Request req = pendingRequests.get(requestCode); if (req != null) { boolean success = true; - for(int r:grantResults) { + for (int r : grantResults) { if (r == PackageManager.PERMISSION_DENIED) { success = false; break; @@ -573,7 +597,8 @@ public void onRequestPermissionResult(int requestCode, String[] permissions, if (success) { executeRequest(req); } else { - pendingRequests.resolveWithFailure(req, createErrorObject(CAPTURE_PERMISSION_DENIED, "Permission denied.")); + pendingRequests.resolveWithFailure(req, + createErrorObject(CAPTURE_PERMISSION_DENIED, "Permission denied.")); } } } diff --git a/src/ios/CDVCapture.m b/src/ios/CDVCapture.m index f2fe258c..2ee3e02c 100644 --- a/src/ios/CDVCapture.m +++ b/src/ios/CDVCapture.m @@ -269,6 +269,14 @@ - (void)captureVideo:(CDVInvokedUrlCommand*)command // pickerController.videoQuality = UIImagePickerControllerQualityTypeHigh; // pickerController.cameraDevice = UIImagePickerControllerCameraDeviceRear; // pickerController.cameraFlashMode = UIImagePickerControllerCameraFlashModeAuto; + + NSNumber* cameraDirection = [options objectForKey:@"cameraDirection"]; + + // check if the value of camera direction is 1 + // if that is the case use the front face camera + if([cameraDirection intValue] == 1) { + pickerController.cameraDevice = UIImagePickerControllerCameraDeviceFront; + } } // CDVImagePicker specific property pickerController.callbackId = callbackId; diff --git a/www/CameraConstants.js b/www/CameraConstants.js new file mode 100644 index 00000000..43750602 --- /dev/null +++ b/www/CameraConstants.js @@ -0,0 +1,10 @@ +/** + * @module MediaCapture + */ +module.exports = { + /** + * Camera direction constants + */ + CAMERA_BACK: 0, + CAMERA_FRONT: 1 +};