diff --git a/src/android/com/emj365/plugins/AudioRecorderAPI.java b/src/android/com/emj365/plugins/AudioRecorderAPI.java index 362d9e9..99f9b21 100644 --- a/src/android/com/emj365/plugins/AudioRecorderAPI.java +++ b/src/android/com/emj365/plugins/AudioRecorderAPI.java @@ -4,6 +4,7 @@ import org.apache.cordova.CordovaPlugin; import org.apache.cordova.PluginResult; import org.json.JSONArray; +import org.json.JSONObject; import org.json.JSONException; import android.media.MediaRecorder; import android.media.MediaPlayer; @@ -11,66 +12,66 @@ import android.os.CountDownTimer; import android.os.Environment; import android.content.Context; + +import java.util.Date; import java.util.UUID; import java.io.FileInputStream; import java.io.File; import java.io.IOException; +import org.apache.cordova.CordovaInterface; +import org.apache.cordova.LOG; +import android.content.pm.PackageManager; + +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; +import java.util.Arrays; + public class AudioRecorderAPI extends CordovaPlugin { private MediaRecorder myRecorder; private String outputFile; private CountDownTimer countDowntimer; + private Integer seconds; + private Integer duration; + private CallbackContext callbackContext; + private static final String LOG_TAG = "CordovaPermissionHelper"; + public static final int PERMISSION_DENIED_ERROR = 20; + private long lastBeginRecord = 0L; @Override public boolean execute(String action, JSONArray args, final CallbackContext callbackContext) throws JSONException { + this.callbackContext = callbackContext; Context context = cordova.getActivity().getApplicationContext(); - Integer seconds; - if (args.length() >= 1) { - seconds = args.getInt(0); - } else { - seconds = 7; - } + this.callbackContext = callbackContext; if (action.equals("record")) { - outputFile = context.getFilesDir().getAbsoluteFile() + "/" - + UUID.randomUUID().toString() + ".m4a"; - myRecorder = new MediaRecorder(); - myRecorder.setAudioSource(MediaRecorder.AudioSource.MIC); - myRecorder.setOutputFormat(MediaRecorder.OutputFormat.MPEG_4); - myRecorder.setAudioEncoder(MediaRecorder.AudioEncoder.AAC); - myRecorder.setAudioSamplingRate(44100); - myRecorder.setAudioChannels(1); - myRecorder.setAudioEncodingBitRate(32000); - myRecorder.setOutputFile(outputFile); - - try { - myRecorder.prepare(); - myRecorder.start(); - } catch (final Exception e) { - cordova.getThreadPool().execute(new Runnable() { - public void run() { - callbackContext.error(e.getMessage()); - } - }); - return false; + if (args.length() >= 1) { + this.seconds = args.getInt(0); + } else { + this.seconds = 7; } - - countDowntimer = new CountDownTimer(seconds * 1000, 1000) { - public void onTick(long millisUntilFinished) {} - public void onFinish() { - stopRecord(callbackContext); - } - }; - countDowntimer.start(); + this.record(); return true; } if (action.equals("stop")) { - countDowntimer.cancel(); stopRecord(callbackContext); return true; } + if (action.equals("checkPermission")) { + Boolean has = this.hasPermission(this, "android.permission.RECORD_AUDIO"); + if(!has) { + this.requestPermissions(this, 0, new String[] {"android.permission.RECORD_AUDIO"}); + PluginResult r = new PluginResult(PluginResult.Status.NO_RESULT); + r.setKeepCallback(true); + callbackContext.sendPluginResult(r); + } else { + callbackContext.success(); + } + return true; + } + if (action.equals("playback")) { MediaPlayer mp = new MediaPlayer(); mp.setAudioStreamType(AudioManager.STREAM_MUSIC); @@ -105,14 +106,147 @@ public void onCompletion(MediaPlayer mp) { return false; } - private void stopRecord(final CallbackContext callbackContext) { - myRecorder.stop(); - myRecorder.release(); - cordova.getThreadPool().execute(new Runnable() { - public void run() { - callbackContext.success(outputFile); + /** + * Checks at runtime to see if the application has been granted a permission. This is a helper + * method alternative to cordovaInterface.hasPermission() that does not require the project to + * be built with cordova-android 5.0.0+ + * + * @param plugin The plugin the permission is being checked against + * @param permission The permission to be checked + * + * @return True if the permission has already been granted and false otherwise + */ + public boolean hasPermission(CordovaPlugin plugin, String permission) { + try { + Method hasPermission = CordovaInterface.class.getDeclaredMethod("hasPermission", String.class); + + // If there is no exception, then this is cordova-android 5.0.0+ + return (Boolean) hasPermission.invoke(plugin.cordova, permission); + } catch (NoSuchMethodException noSuchMethodException) { + // cordova-android version is less than 5.0.0, so permission is implicitly granted + LOG.d(LOG_TAG, "No need to check for permission " + permission); + return true; + } catch (IllegalAccessException illegalAccessException) { + // Should never be caught; this is a public method + LOG.e(LOG_TAG, "IllegalAccessException when checking permission " + permission, illegalAccessException); + } catch(InvocationTargetException invocationTargetException) { + // This method does not throw any exceptions, so this should never be caught + LOG.e(LOG_TAG, "invocationTargetException when checking permission " + permission, invocationTargetException); + } + return false; + } + + public void requestPermissions(CordovaPlugin plugin, int requestCode, String[] permissions) { + try { + Method requestPermission = CordovaInterface.class.getDeclaredMethod( + "requestPermissions", CordovaPlugin.class, int.class, String[].class); + + // If there is no exception, then this is cordova-android 5.0.0+ + requestPermission.invoke(plugin.cordova, plugin, requestCode, permissions); + } catch (NoSuchMethodException noSuchMethodException) { + // cordova-android version is less than 5.0.0, so permission is implicitly granted + LOG.d(LOG_TAG, "No need to request permissions " + Arrays.toString(permissions)); + + // Notify the plugin that all were granted by using more reflection + // deliverPermissionResult(plugin, requestCode, permissions); + } catch (IllegalAccessException illegalAccessException) { + // Should never be caught; this is a public method + LOG.e(LOG_TAG, "IllegalAccessException when requesting permissions " + Arrays.toString(permissions), illegalAccessException); + } catch(InvocationTargetException invocationTargetException) { + // This method does not throw any exceptions, so this should never be caught + LOG.e(LOG_TAG, "invocationTargetException when requesting permissions " + Arrays.toString(permissions), invocationTargetException); + } + } + + public void onRequestPermissionResult(int requestCode, String[] permissions, + int[] grantResults) throws JSONException + { + for(int r:grantResults) + { + if(r == PackageManager.PERMISSION_DENIED) + { + this.callbackContext.sendPluginResult(new PluginResult(PluginResult.Status.ERROR, PERMISSION_DENIED_ERROR)); + return; + } } - }); + this.callbackContext.success(); + } + + public void record() { + Context context = cordova.getActivity().getApplicationContext(); + + outputFile = context.getFilesDir().getAbsoluteFile() + "/" + + UUID.randomUUID().toString() + ".m4a"; + myRecorder = new MediaRecorder(); + myRecorder.setAudioSource(MediaRecorder.AudioSource.MIC); + myRecorder.setOutputFormat(MediaRecorder.OutputFormat.MPEG_4); + myRecorder.setAudioEncoder(MediaRecorder.AudioEncoder.AAC); + myRecorder.setAudioSamplingRate(44100); + myRecorder.setAudioChannels(1); + myRecorder.setAudioEncodingBitRate(32000); + myRecorder.setOutputFile(outputFile); + + // cordova.getThreadPool().execute(new Runnable() { + // public void run() { + try { + lastBeginRecord = (new Date()).getTime(); + myRecorder.prepare(); + myRecorder.start(); + } catch (final Exception e) { + cordova.getThreadPool().execute(new Runnable() { + public void run() { + callbackContext.error(e.getMessage()); + } + }); + } + // } + // }); + + countDowntimer = new CountDownTimer(this.seconds * 1000, 1000) { + public void onTick(long millisUntilFinished) { + duration = seconds - (int) millisUntilFinished / 1000; + } + public void onFinish() { + stopRecord(callbackContext); + } + }; + countDowntimer.start(); + } + + + private void stopRecord(final CallbackContext callbackContext) { + try { + long curTime = (new Date()).getTime(); + if (curTime - lastBeginRecord < 1000) return; + countDowntimer.cancel(); + myRecorder.stop(); + myRecorder.release(); + cordova.getThreadPool().execute(new Runnable() { + public void run() { + try { + callbackContext.success(composeCallback()); + } catch (JSONException e) { + callbackContext.error(e.getMessage()); + } + } + }); + } catch (Exception e) { + LOG.e(LOG_TAG, e.toString()); + } + } + + private JSONObject composeCallback() throws JSONException { + File f = new File(outputFile); + if(f.exists() && f.length() > 0) + { + String json = "{ path: '" + outputFile + "', duration: " + duration + " }"; + return new JSONObject(json); + } + else + { + String json = "{ path: '', duration: 0 }"; + return new JSONObject(json); + } } } diff --git a/src/ios/AudioRecorderAPI.h b/src/ios/AudioRecorderAPI.h index f8dfd06..c709a21 100644 --- a/src/ios/AudioRecorderAPI.h +++ b/src/ios/AudioRecorderAPI.h @@ -9,9 +9,11 @@ CDVPluginResult *pluginResult; CDVInvokedUrlCommand *_command; } +@property (nonatomic, strong) NSString *currentCallbackId; - (void)record:(CDVInvokedUrlCommand*)command; - (void)stop:(CDVInvokedUrlCommand*)command; - (void)playback:(CDVInvokedUrlCommand*)command; +- (void)checkPermission:(CDVInvokedUrlCommand*)command; @end diff --git a/src/ios/AudioRecorderAPI.m b/src/ios/AudioRecorderAPI.m index f75f9f3..e6de0fc 100644 --- a/src/ios/AudioRecorderAPI.m +++ b/src/ios/AudioRecorderAPI.m @@ -97,13 +97,60 @@ - (void)audioRecorderDidFinishRecording:(AVAudioRecorder *)recorder successfully NSURL *url = [NSURL fileURLWithPath: recorderFilePath]; NSError *err = nil; NSData *audioData = [NSData dataWithContentsOfFile:[url path] options: 0 error:&err]; + AVURLAsset* audioAsset = [AVURLAsset URLAssetWithURL:[NSURL fileURLWithPath:recorderFilePath] options:nil]; + int audioDuration = CMTimeGetSeconds(audioAsset.duration); if(!audioData) { NSLog(@"audio data: %@ %d %@", [err domain], [err code], [[err userInfo] description]); } else { - NSLog(@"recording saved: %@", recorderFilePath); - pluginResult = [CDVPluginResult resultWithStatus:CDVCommandStatus_OK messageAsString:recorderFilePath]; + NSLog(@"recording saved: %@ %d", recorderFilePath, audioDuration); + pluginResult = [CDVPluginResult resultWithStatus:CDVCommandStatus_OK messageAsDictionary:@{@"path": recorderFilePath, @"duration": [NSNumber numberWithInt:audioDuration]}]; [self.commandDelegate sendPluginResult:pluginResult callbackId:_command.callbackId]; } } +// Delegate for camera permission UIAlertView +- (void)alertView:(UIAlertView *)alertView clickedButtonAtIndex:(NSInteger)buttonIndex +{ + // If Settings button (on iOS 8), open the settings app + if (buttonIndex == 1) { +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wtautological-pointer-compare" + if (&UIApplicationOpenSettingsURLString != NULL) { + [[UIApplication sharedApplication] openURL:[NSURL URLWithString:UIApplicationOpenSettingsURLString]]; + } + +#pragma clang diagnostic pop + } + +} + +- (void)checkPermission:(CDVInvokedUrlCommand*)command { + __weak AudioRecorderAPI* weakSelf = self; + self.currentCallbackId = command.callbackId; + + if ([[AVAudioSession sharedInstance] respondsToSelector:@selector(requestRecordPermission:)]){ + + [[AVAudioSession sharedInstance] requestRecordPermission:^(BOOL granted) { + if (granted) { + CDVPluginResult* result = [CDVPluginResult resultWithStatus:CDVCommandStatus_OK]; + [self.commandDelegate sendPluginResult:result callbackId:self.currentCallbackId]; + }else { + dispatch_async(dispatch_get_main_queue(), ^{ + [[[UIAlertView alloc] initWithTitle:[[NSBundle mainBundle] + objectForInfoDictionaryKey:@"CFBundleDisplayName"] + message:NSLocalizedString(@"Access to the audio has been prohibited; please enable it in the Settings app to continue.", nil) + delegate:weakSelf + cancelButtonTitle:NSLocalizedString(@"OK", nil) + otherButtonTitles:NSLocalizedString(@"Setting", nil), nil] show]; + }); + CDVPluginResult* result = [CDVPluginResult resultWithStatus:CDVCommandStatus_ERROR]; + [self.commandDelegate sendPluginResult:result callbackId:self.currentCallbackId]; + } + }]; + + } + + } + + @end diff --git a/www/AudioRecorderAPI.js b/www/AudioRecorderAPI.js index ee70b77..9bcc415 100755 --- a/www/AudioRecorderAPI.js +++ b/www/AudioRecorderAPI.js @@ -21,4 +21,8 @@ AudioRecorderAPI.install = function () { return window.plugins.audioRecorderAPI; }; +AudioRecorderAPI.prototype.checkPermission = function (successCallback, errorCallback) { + cordova.exec(successCallback, errorCallback, "AudioRecorderAPI", "checkPermission", []); +}; + cordova.addConstructor(AudioRecorderAPI.install);