From e766188689dde13a1717f0a5d1e9f5bf482363e0 Mon Sep 17 00:00:00 2001 From: macdonst Date: Fri, 11 Mar 2011 16:14:47 -0500 Subject: [PATCH] W3C Media Capture API An implementation of the W3C Media Capture spec: http://dev.w3.org/2009/dap/camera/Overview-API Capture operations are supported for audio, video, and images. Each capture operation launches the native audio recorder, video recorder, or camera application, respectively. --- framework/assets/js/capture.js | 187 ++++++++++++ framework/assets/js/device.js | 4 +- framework/assets/js/file.js | 5 - framework/src/com/phonegap/Capture.java | 346 ++++++++++++++++++++++ framework/src/com/phonegap/DroidGap.java | 1 + framework/src/com/phonegap/FileUtils.java | 4 +- 6 files changed, 539 insertions(+), 8 deletions(-) create mode 100644 framework/assets/js/capture.js create mode 100644 framework/src/com/phonegap/Capture.java diff --git a/framework/assets/js/capture.js b/framework/assets/js/capture.js new file mode 100644 index 00000000..9d9dfc63 --- /dev/null +++ b/framework/assets/js/capture.js @@ -0,0 +1,187 @@ +/* + * PhoneGap is available under *either* the terms of the modified BSD license *or* the + * MIT License (2008). See http://opensource.org/licenses/alphabetical for full text. + * + * Copyright (c) 2005-2010, Nitobi Software Inc. + * Copyright (c) 2010-2011, IBM Corporation + */ + +/** + * The CaptureError interface encapsulates all errors in the Capture API. + */ +function CaptureError() { + this.code = null; +}; + +// Capture error codes +CaptureError.CAPTURE_INTERNAL_ERR = 0; +CaptureError.CAPTURE_APPLICATION_BUSY = 1; +CaptureError.CAPTURE_INVALID_ARGUMENT = 2; +CaptureError.CAPTURE_NO_MEDIA_FILES = 3; +CaptureError.CAPTURE_NOT_SUPPORTED = 20; + +/** + * The Capture interface exposes an interface to the camera and microphone of the hosting device. + */ +function Capture() { + this.supportedAudioFormats = []; + this.supportedImageFormats = []; + this.supportedVideoFormats = []; +}; + +/** + * Launch audio recorder application for recording audio clip(s). + * + * @param {Function} successCB + * @param {Function} errorCB + * @param {CaptureAudioOptions} options + */ +Capture.prototype.captureAudio = function(successCallback, errorCallback, options) { + PhoneGap.exec(successCallback, errorCallback, "Capture", "captureAudio", [options]); +}; + +/** + * Launch camera application for taking image(s). + * + * @param {Function} successCB + * @param {Function} errorCB + * @param {CaptureImageOptions} options + */ +Capture.prototype.captureImage = function(successCallback, errorCallback, options) { + PhoneGap.exec(successCallback, errorCallback, "Capture", "captureImage", [options]); +}; + +/** + * Launch camera application for taking image(s). + * + * @param {Function} successCB + * @param {Function} errorCB + * @param {CaptureImageOptions} options + */ +Capture.prototype._castMediaFile = function(pluginResult) { + var mediaFiles = []; + var i; + for (i=0; i= limit) { + // Send Uri back to JavaScript for listening to audio + this.success(new PluginResult(PluginResult.Status.OK, results, "navigator.device.capture._castMediaFile"), this.callbackId); + } else { + // still need to capture more audio clips + captureAudio(); + } + } else if (requestCode == CAPTURE_IMAGE) { + // For some reason if I try to do: + // Uri data = intent.getData(); + // It crashes in the emulator and on my phone with a null pointer exception + // To work around it I had to grab the code from CameraLauncher.java + try { + // Read in bitmap of captured image + Bitmap bitmap = android.provider.MediaStore.Images.Media.getBitmap(this.ctx.getContentResolver(), imageUri); + + // Create entry in media store for image + // (Don't use insertImage() because it uses default compression setting of 50 - no way to change it) + ContentValues values = new ContentValues(); + values.put(android.provider.MediaStore.Images.Media.MIME_TYPE, "image/jpeg"); + Uri uri = null; + try { + uri = this.ctx.getContentResolver().insert(android.provider.MediaStore.Images.Media.EXTERNAL_CONTENT_URI, values); + } catch (UnsupportedOperationException e) { + System.out.println("Can't write to external media storage."); + try { + uri = this.ctx.getContentResolver().insert(android.provider.MediaStore.Images.Media.INTERNAL_CONTENT_URI, values); + } catch (UnsupportedOperationException ex) { + System.out.println("Can't write to internal media storage."); + this.fail("Error capturing image - no media storage found."); + return; + } + } + + // Add compressed version of captured image to returned media store Uri + OutputStream os = this.ctx.getContentResolver().openOutputStream(uri); + bitmap.compress(Bitmap.CompressFormat.JPEG, 100, os); + os.close(); + + bitmap.recycle(); + bitmap = null; + System.gc(); + + // Add image to results + results.put(createMediaFile(uri)); + + if (results.length() >= limit) { + // Send Uri back to JavaScript for viewing image + this.success(new PluginResult(PluginResult.Status.OK, results, "navigator.device.capture._castMediaFile"), this.callbackId); + } else { + // still need to capture more images + captureImage(); + } + } catch (IOException e) { + e.printStackTrace(); + this.fail("Error capturing image."); + } + } else if (requestCode == CAPTURE_VIDEO) { + // Get the uri of the video clip + Uri data = intent.getData(); + // create a file object from the uri + results.put(createMediaFile(data)); + + if (results.length() >= limit) { + // Send Uri back to JavaScript for viewing video + this.success(new PluginResult(PluginResult.Status.OK, results, "navigator.device.capture._castMediaFile"), this.callbackId); + } else { + // still need to capture more video clips + captureVideo(duration); + } + } + } + // If canceled + else if (resultCode == Activity.RESULT_CANCELED) { + // If we have partial results send them back to the user + if (results.length() > 0) { + this.success(new PluginResult(PluginResult.Status.OK, results, "navigator.device.capture._castMediaFile"), this.callbackId); + } + // user canceled the action + else { + this.fail("Canceled."); + } + } + // If something else + else { + // If we have partial results send them back to the user + if (results.length() > 0) { + this.success(new PluginResult(PluginResult.Status.OK, results, "navigator.device.capture._castMediaFile"), this.callbackId); + } + // something bad happened + else { + this.fail("Did not complete!"); + } + } + } + + /** + * Creates a JSONObject that represents a File from the Uri + * + * @param data the Uri of the audio/image/video + * @return a JSONObject that represents a File + */ + private JSONObject createMediaFile(Uri data) { + File fp = new File(getRealPathFromURI(data)); + + JSONObject obj = new JSONObject(); + + try { + // File properties + obj.put("name", fp.getName()); + obj.put("fullPath", fp.getAbsolutePath()); + obj.put("type", FileUtils.getMimeType(fp.getAbsolutePath())); + obj.put("lastModifiedDate", fp.lastModified()); + obj.put("size", fp.length()); + } catch (JSONException e) { + // this will never happen + e.printStackTrace(); + } + + return obj; + } + + /** + * Queries the media store to find out what the file path is for the Uri we supply + * + * @param contentUri the Uri of the audio/image/video + * @return the full path to the file + */ + private String getRealPathFromURI(Uri contentUri) { + String[] proj = { _DATA }; + Cursor cursor = this.ctx.managedQuery(contentUri, proj, null, null, null); + int column_index = cursor.getColumnIndexOrThrow(_DATA); + cursor.moveToFirst(); + return cursor.getString(column_index); + } + + /** + * Send error message to JavaScript. + * + * @param err + */ + public void fail(String err) { + this.error(new PluginResult(PluginResult.Status.ERROR, err), this.callbackId); + } +} \ No newline at end of file diff --git a/framework/src/com/phonegap/DroidGap.java b/framework/src/com/phonegap/DroidGap.java index 9ed17a03..74102e00 100755 --- a/framework/src/com/phonegap/DroidGap.java +++ b/framework/src/com/phonegap/DroidGap.java @@ -287,6 +287,7 @@ public class DroidGap extends PhonegapActivity { this.addService("Storage", "com.phonegap.Storage"); this.addService("Temperature", "com.phonegap.TempListener"); this.addService("FileTransfer", "com.phonegap.FileTransfer"); + this.addService("Capture", "com.phonegap.Capture"); } /** diff --git a/framework/src/com/phonegap/FileUtils.java b/framework/src/com/phonegap/FileUtils.java index b88fc962..94f7596c 100755 --- a/framework/src/com/phonegap/FileUtils.java +++ b/framework/src/com/phonegap/FileUtils.java @@ -3,7 +3,7 @@ * MIT License (2008). See http://opensource.org/licenses/alphabetical for full text. * * Copyright (c) 2005-2010, Nitobi Software Inc. - * Copyright (c) 2010, IBM Corporation + * Copyright (c) 2010-2011, IBM Corporation */ package com.phonegap; @@ -942,7 +942,7 @@ public class FileUtils extends Plugin { * @param filename * @return a mime type */ - private String getMimeType(String filename) { + public static String getMimeType(String filename) { MimeTypeMap map = MimeTypeMap.getSingleton(); return map.getMimeTypeFromExtension(map.getFileExtensionFromUrl(filename)); }