Compare commits
15 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 0d37267eb4 | |||
| 88ee99011c | |||
| a6cc9271a0 | |||
| b3430e6f80 | |||
| 888a9661fb | |||
| 15f66bf5cd | |||
| 33ffb17c97 | |||
| a830b3e08b | |||
| 706a3aa73e | |||
| 9f9e5ef4a9 | |||
| b8b53c6254 | |||
| 0436928275 | |||
| 19feee9cb0 | |||
| 69c687e0cf | |||
| b0ee9dd905 |
@@ -1,20 +1,20 @@
|
||||
<!---
|
||||
license: Licensed to the Apache Software Foundation (ASF) under one
|
||||
or more contributor license agreements. See the NOTICE file
|
||||
distributed with this work for additional information
|
||||
regarding copyright ownership. The ASF licenses this file
|
||||
to you under the Apache License, Version 2.0 (the
|
||||
"License"); you may not use this file except in compliance
|
||||
with the License. You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing,
|
||||
software distributed under the License is distributed on an
|
||||
"AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
KIND, either express or implied. See the License for the
|
||||
specific language governing permissions and limitations
|
||||
under the License.
|
||||
# license: Licensed to the Apache Software Foundation (ASF) under one
|
||||
# or more contributor license agreements. See the NOTICE file
|
||||
# distributed with this work for additional information
|
||||
# regarding copyright ownership. The ASF licenses this file
|
||||
# to you under the Apache License, Version 2.0 (the
|
||||
# "License"); you may not use this file except in compliance
|
||||
# with the License. You may obtain a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing,
|
||||
# software distributed under the License is distributed on an
|
||||
# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
# KIND, either express or implied. See the License for the
|
||||
# specific language governing permissions and limitations
|
||||
# under the License.
|
||||
-->
|
||||
|
||||
# cordova-plugin-camera
|
||||
@@ -35,6 +35,16 @@ Although the object is attached to the global scoped `navigator`, it is not avai
|
||||
|
||||
cordova plugin add cordova-plugin-camera
|
||||
|
||||
## API
|
||||
- Camera
|
||||
- navigator.camera.getPicture(success, fail, options)
|
||||
- CameraOptions
|
||||
- CameraPopoverHandle
|
||||
- CameraPopoverOptions
|
||||
- navigator.camera.cleanup
|
||||
|
||||
|
||||
|
||||
## navigator.camera.getPicture
|
||||
|
||||
Takes a photo using the camera, or retrieves a photo from the device's
|
||||
@@ -43,9 +53,9 @@ base64-encoded `String`, or as the URI for the image file. The method
|
||||
itself returns a `CameraPopoverHandle` object that can be used to
|
||||
reposition the file selection popover.
|
||||
|
||||
navigator.camera.getPicture( cameraSuccess, cameraError, cameraOptions );
|
||||
navigator.camera.getPicture(cameraSuccess, cameraError, cameraOptions);
|
||||
|
||||
### Description
|
||||
#### Description
|
||||
|
||||
The `camera.getPicture` function opens the device's default camera
|
||||
application that allows users to snap pictures. This behavior occurs
|
||||
@@ -83,69 +93,11 @@ quality, even if a `quality` parameter is specified. To avoid common
|
||||
memory problems, set `Camera.destinationType` to `FILE_URI` rather
|
||||
than `DATA_URL`.
|
||||
|
||||
### Supported Platforms
|
||||
#### Supported Platforms
|
||||
|
||||
- Amazon Fire OS
|
||||
- Android
|
||||
- BlackBerry 10
|
||||
- Browser
|
||||
- Firefox OS
|
||||
- iOS
|
||||
- Tizen
|
||||
- Windows Phone 7 and 8
|
||||
- Windows 8
|
||||
- Windows
|
||||
        
|
||||
|
||||
### Preferences (iOS)
|
||||
|
||||
- __CameraUsesGeolocation__ (boolean, defaults to false). For capturing JPEGs, set to true to get geolocation data in the EXIF header. This will trigger a request for geolocation permissions if set to true.
|
||||
|
||||
<preference name="CameraUsesGeolocation" value="false" />
|
||||
|
||||
|
||||
### Amazon Fire OS Quirks
|
||||
|
||||
Amazon Fire OS uses intents to launch the camera activity on the device to capture
|
||||
images, and on phones with low memory, the Cordova activity may be killed. In this
|
||||
scenario, the image may not appear when the cordova activity is restored.
|
||||
|
||||
### Android Quirks
|
||||
|
||||
Android uses intents to launch the camera activity on the device to capture
|
||||
images, and on phones with low memory, the Cordova activity may be killed. In this
|
||||
scenario, the image may not appear when the Cordova activity is restored.
|
||||
|
||||
### Browser Quirks
|
||||
|
||||
Can only return photos as base64-encoded image.
|
||||
|
||||
### Firefox OS Quirks
|
||||
|
||||
Camera plugin is currently implemented using [Web Activities](https://hacks.mozilla.org/2013/01/introducing-web-activities/).
|
||||
|
||||
### iOS Quirks
|
||||
|
||||
Including a JavaScript `alert()` in either of the callback functions
|
||||
can cause problems. Wrap the alert within a `setTimeout()` to allow
|
||||
the iOS image picker or popover to fully close before the alert
|
||||
displays:
|
||||
|
||||
setTimeout(function() {
|
||||
// do your thing here!
|
||||
}, 0);
|
||||
|
||||
### Windows Phone 7 Quirks
|
||||
|
||||
Invoking the native camera application while the device is connected
|
||||
via Zune does not work, and triggers an error callback.
|
||||
|
||||
### Tizen Quirks
|
||||
|
||||
Tizen only supports a `destinationType` of
|
||||
`Camera.DestinationType.FILE_URI` and a `sourceType` of
|
||||
`Camera.PictureSourceType.PHOTOLIBRARY`.
|
||||
|
||||
### Example
|
||||
#### Example
|
||||
|
||||
Take a photo and retrieve it as a base64-encoded image:
|
||||
|
||||
@@ -176,6 +128,54 @@ Take a photo and retrieve the image's file location:
|
||||
alert('Failed because: ' + message);
|
||||
}
|
||||
|
||||
#### Preferences (iOS)
|
||||
|
||||
- __CameraUsesGeolocation__ (boolean, defaults to false). For capturing JPEGs, set to true to get geolocation data in the EXIF header. This will trigger a request for geolocation permissions if set to true.
|
||||
|
||||
<preference name="CameraUsesGeolocation" value="false" />
|
||||
|
||||
#### Amazon Fire OS Quirks
|
||||
|
||||
Amazon Fire OS uses intents to launch the camera activity on the device to capture
|
||||
images, and on phones with low memory, the Cordova activity may be killed. In this
|
||||
scenario, the image may not appear when the cordova activity is restored.
|
||||
|
||||
#### Android Quirks
|
||||
|
||||
Android uses intents to launch the camera activity on the device to capture
|
||||
images, and on phones with low memory, the Cordova activity may be killed. In this
|
||||
scenario, the image may not appear when the Cordova activity is restored.
|
||||
|
||||
#### Browser Quirks
|
||||
|
||||
Can only return photos as base64-encoded image.
|
||||
|
||||
#### Firefox OS Quirks
|
||||
|
||||
Camera plugin is currently implemented using [Web Activities](https://hacks.mozilla.org/2013/01/introducing-web-activities/).
|
||||
|
||||
#### iOS Quirks
|
||||
|
||||
Including a JavaScript `alert()` in either of the callback functions
|
||||
can cause problems. Wrap the alert within a `setTimeout()` to allow
|
||||
the iOS image picker or popover to fully close before the alert
|
||||
displays:
|
||||
|
||||
setTimeout(function() {
|
||||
// do your thing here!
|
||||
}, 0);
|
||||
|
||||
#### Windows Phone 7 Quirks
|
||||
|
||||
Invoking the native camera application while the device is connected
|
||||
via Zune does not work, and triggers an error callback.
|
||||
|
||||
#### Tizen Quirks
|
||||
|
||||
Tizen only supports a `destinationType` of
|
||||
`Camera.DestinationType.FILE_URI` and a `sourceType` of
|
||||
`Camera.PictureSourceType.PHOTOLIBRARY`.
|
||||
|
||||
## CameraOptions
|
||||
|
||||
Optional parameters to customize the camera settings.
|
||||
@@ -190,8 +190,6 @@ Optional parameters to customize the camera settings.
|
||||
popoverOptions: CameraPopoverOptions,
|
||||
saveToPhotoAlbum: false };
|
||||
|
||||
### Options
|
||||
|
||||
- __quality__: Quality of the saved image, expressed as a range of 0-100, where 100 is typically full resolution with no loss from file compression. The default is 50. _(Number)_ (Note that information about the camera's resolution is unavailable.)
|
||||
|
||||
- __destinationType__: Choose the format of the return value. The default is FILE_URI. Defined in `navigator.camera.DestinationType` _(Number)_
|
||||
@@ -244,7 +242,7 @@ Optional parameters to customize the camera settings.
|
||||
FRONT : 1 // Use the front-facing camera
|
||||
};
|
||||
|
||||
### Amazon Fire OS Quirks
|
||||
#### Amazon Fire OS Quirks
|
||||
|
||||
- Any `cameraDirection` value results in a back-facing photo.
|
||||
|
||||
@@ -252,7 +250,7 @@ Optional parameters to customize the camera settings.
|
||||
|
||||
- `Camera.PictureSourceType.PHOTOLIBRARY` and `Camera.PictureSourceType.SAVEDPHOTOALBUM` both display the same photo album.
|
||||
|
||||
### Android Quirks
|
||||
#### Android Quirks
|
||||
|
||||
- Any `cameraDirection` value results in a back-facing photo.
|
||||
|
||||
@@ -261,7 +259,7 @@ with the Google Plus Photos application. Other crops may not work.
|
||||
|
||||
- `Camera.PictureSourceType.PHOTOLIBRARY` and `Camera.PictureSourceType.SAVEDPHOTOALBUM` both display the same photo album.
|
||||
|
||||
### BlackBerry 10 Quirks
|
||||
#### BlackBerry 10 Quirks
|
||||
|
||||
- Ignores the `quality` parameter.
|
||||
|
||||
@@ -273,7 +271,7 @@ with the Google Plus Photos application. Other crops may not work.
|
||||
|
||||
- Ignores the `cameraDirection` parameter.
|
||||
|
||||
### Firefox OS Quirks
|
||||
#### Firefox OS Quirks
|
||||
|
||||
- Ignores the `quality` parameter.
|
||||
|
||||
@@ -293,19 +291,19 @@ with the Google Plus Photos application. Other crops may not work.
|
||||
|
||||
- Ignores the `cameraDirection` parameter.
|
||||
|
||||
### iOS Quirks
|
||||
#### iOS Quirks
|
||||
|
||||
- Set `quality` below 50 to avoid memory errors on some devices.
|
||||
|
||||
- When using `destinationType.FILE_URI`, photos are saved in the application's temporary directory. The contents of the application's temporary directory is deleted when the application ends.
|
||||
|
||||
### Tizen Quirks
|
||||
#### Tizen Quirks
|
||||
|
||||
- options not supported
|
||||
|
||||
- always returns a FILE URI
|
||||
|
||||
### Windows Phone 7 and 8 Quirks
|
||||
#### Windows Phone 7 and 8 Quirks
|
||||
|
||||
- Ignores the `allowEdit` parameter.
|
||||
|
||||
@@ -327,7 +325,7 @@ onError callback function that provides an error message.
|
||||
// Show a helpful message
|
||||
}
|
||||
|
||||
### Parameters
|
||||
#### Description
|
||||
|
||||
- __message__: The message is provided by the device's native code. _(String)_
|
||||
|
||||
@@ -340,11 +338,11 @@ onSuccess callback function that provides the image data.
|
||||
// Do something with the image
|
||||
}
|
||||
|
||||
### Parameters
|
||||
#### Description
|
||||
|
||||
- __imageData__: Base64 encoding of the image data, _or_ the image file URI, depending on `cameraOptions` in effect. _(String)_
|
||||
|
||||
### Example
|
||||
#### Example
|
||||
|
||||
// Show image
|
||||
//
|
||||
@@ -358,23 +356,15 @@ onSuccess callback function that provides the image data.
|
||||
|
||||
A handle to the popover dialog created by `navigator.camera.getPicture`.
|
||||
|
||||
### Methods
|
||||
#### Description
|
||||
|
||||
- __setPosition__: Set the position of the popover.
|
||||
- __setPosition__: Set the position of the popover. Takes the `CameraPopoverOptions` that specify the new position.
|
||||
|
||||
### Supported Platforms
|
||||
#### Supported Platforms
|
||||
|
||||
- iOS
|
||||
        
|
||||
|
||||
### setPosition
|
||||
|
||||
Set the position of the popover.
|
||||
|
||||
__Parameters__:
|
||||
|
||||
- `cameraPopoverOptions`: the `CameraPopoverOptions` that specify the new position
|
||||
|
||||
### Example
|
||||
#### Example
|
||||
|
||||
var cameraPopoverHandle = navigator.camera.getPicture(onSuccess, onFail,
|
||||
{ destinationType: Camera.DestinationType.FILE_URI,
|
||||
@@ -388,7 +378,6 @@ __Parameters__:
|
||||
cameraPopoverHandle.setPosition(cameraPopoverOptions);
|
||||
}
|
||||
|
||||
|
||||
## CameraPopoverOptions
|
||||
|
||||
iOS-only parameters that specify the anchor element location and arrow
|
||||
@@ -402,7 +391,7 @@ or album.
|
||||
arrowDir : Camera.PopoverArrowDirection.ARROW_ANY
|
||||
};
|
||||
|
||||
### CameraPopoverOptions
|
||||
#### Description
|
||||
|
||||
- __x__: x pixel coordinate of screen element onto which to anchor the popover. _(Number)_
|
||||
|
||||
@@ -434,18 +423,18 @@ storage.
|
||||
|
||||
navigator.camera.cleanup( cameraSuccess, cameraError );
|
||||
|
||||
### Description
|
||||
#### Description
|
||||
|
||||
Removes intermediate image files that are kept in temporary storage
|
||||
after calling `camera.getPicture`. Applies only when the value of
|
||||
`Camera.sourceType` equals `Camera.PictureSourceType.CAMERA` and the
|
||||
`Camera.destinationType` equals `Camera.DestinationType.FILE_URI`.
|
||||
|
||||
### Supported Platforms
|
||||
#### Supported Platforms
|
||||
|
||||
- iOS
|
||||
        
|
||||
|
||||
### Example
|
||||
#### Example
|
||||
|
||||
navigator.camera.cleanup(onSuccess, onFail);
|
||||
|
||||
|
||||
@@ -197,3 +197,12 @@
|
||||
* docs: added 'Windows' to supported platforms
|
||||
* CB-8653 Updated Readme
|
||||
* CB-8659: ios: 4.0.x Compatibility: Remove use of deprecated headers
|
||||
|
||||
### 1.1.0 (May 06, 2015)
|
||||
* CB-8943 fix `PickAndContinue` issue on *Win10Phone*
|
||||
* CB-8253 Fix potential unreleased resources
|
||||
* CB-8909: Remove unused import from File
|
||||
* CB-8404 typo fix `cameraproxy.js`
|
||||
* CB-8404 Rotate camera feed with device orientation
|
||||
* CB-8054 Support taking pictures from file for *WP8*
|
||||
* CB-8405 Use `z-index` instead of `z-order`
|
||||
|
||||
|
After Width: | Height: | Size: 753 B |
|
After Width: | Height: | Size: 716 B |
|
After Width: | Height: | Size: 1009 B |
|
After Width: | Height: | Size: 984 B |
|
After Width: | Height: | Size: 806 B |
|
After Width: | Height: | Size: 776 B |
|
After Width: | Height: | Size: 802 B |
|
After Width: | Height: | Size: 770 B |
|
After Width: | Height: | Size: 965 B |
|
After Width: | Height: | Size: 936 B |
|
After Width: | Height: | Size: 573 B |
|
After Width: | Height: | Size: 550 B |
|
After Width: | Height: | Size: 649 B |
|
After Width: | Height: | Size: 622 B |
|
After Width: | Height: | Size: 784 B |
|
After Width: | Height: | Size: 759 B |
|
After Width: | Height: | Size: 714 B |
|
After Width: | Height: | Size: 679 B |
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "cordova-plugin-camera",
|
||||
"version": "1.0.1-dev",
|
||||
"version": "1.1.1-dev",
|
||||
"description": "Cordova Camera Plugin",
|
||||
"cordova": {
|
||||
"id": "cordova-plugin-camera",
|
||||
|
||||
@@ -22,7 +22,7 @@
|
||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:rim="http://www.blackberry.com/ns/widgets"
|
||||
id="cordova-plugin-camera"
|
||||
version="1.0.1-dev">
|
||||
version="1.1.1-dev">
|
||||
<name>Camera</name>
|
||||
<description>Cordova Camera Plugin</description>
|
||||
<license>Apache 2.0</license>
|
||||
@@ -146,6 +146,7 @@
|
||||
<framework src="AssetsLibrary.framework" />
|
||||
<framework src="MobileCoreServices.framework" />
|
||||
<framework src="CoreGraphics.framework" />
|
||||
<framework src="AVFoundation.framework" />
|
||||
|
||||
<config-file target="*-Info.plist" parent="NSLocationWhenInUseUsageDescription">
|
||||
<string></string>
|
||||
|
||||
@@ -24,6 +24,7 @@ import java.io.FileInputStream;
|
||||
import java.io.FileNotFoundException;
|
||||
import java.io.FileOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.OutputStream;
|
||||
import java.net.URI;
|
||||
import java.text.SimpleDateFormat;
|
||||
@@ -761,16 +762,33 @@ private String ouputModifiedBitmap(Bitmap bitmap, Uri uri) throws IOException {
|
||||
*/
|
||||
private void writeUncompressedImage(Uri uri) throws FileNotFoundException,
|
||||
IOException {
|
||||
FileInputStream fis = new FileInputStream(FileHelper.stripFileProtocol(imageUri.toString()));
|
||||
OutputStream os = this.cordova.getActivity().getContentResolver().openOutputStream(uri);
|
||||
byte[] buffer = new byte[4096];
|
||||
int len;
|
||||
while ((len = fis.read(buffer)) != -1) {
|
||||
os.write(buffer, 0, len);
|
||||
FileInputStream fis = null;
|
||||
OutputStream os = null;
|
||||
try {
|
||||
fis = new FileInputStream(FileHelper.stripFileProtocol(imageUri.toString()));
|
||||
os = this.cordova.getActivity().getContentResolver().openOutputStream(uri);
|
||||
byte[] buffer = new byte[4096];
|
||||
int len;
|
||||
while ((len = fis.read(buffer)) != -1) {
|
||||
os.write(buffer, 0, len);
|
||||
}
|
||||
os.flush();
|
||||
} finally {
|
||||
if (os != null) {
|
||||
try {
|
||||
os.close();
|
||||
} catch (IOException e) {
|
||||
LOG.d(LOG_TAG,"Exception while closing output stream.");
|
||||
}
|
||||
}
|
||||
if (fis != null) {
|
||||
try {
|
||||
fis.close();
|
||||
} catch (IOException e) {
|
||||
LOG.d(LOG_TAG,"Exception while closing file input stream.");
|
||||
}
|
||||
}
|
||||
}
|
||||
os.flush();
|
||||
os.close();
|
||||
fis.close();
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -806,13 +824,39 @@ private String ouputModifiedBitmap(Bitmap bitmap, Uri uri) throws IOException {
|
||||
private Bitmap getScaledBitmap(String imageUrl) throws IOException {
|
||||
// If no new width or height were specified return the original bitmap
|
||||
if (this.targetWidth <= 0 && this.targetHeight <= 0) {
|
||||
return BitmapFactory.decodeStream(FileHelper.getInputStreamFromUriString(imageUrl, cordova));
|
||||
InputStream fileStream = null;
|
||||
Bitmap image = null;
|
||||
try {
|
||||
fileStream = FileHelper.getInputStreamFromUriString(imageUrl, cordova);
|
||||
image = BitmapFactory.decodeStream(fileStream);
|
||||
} finally {
|
||||
if (fileStream != null) {
|
||||
try {
|
||||
fileStream.close();
|
||||
} catch (IOException e) {
|
||||
LOG.d(LOG_TAG,"Exception while closing file input stream.");
|
||||
}
|
||||
}
|
||||
}
|
||||
return image;
|
||||
}
|
||||
|
||||
// figure out the original width and height of the image
|
||||
BitmapFactory.Options options = new BitmapFactory.Options();
|
||||
options.inJustDecodeBounds = true;
|
||||
BitmapFactory.decodeStream(FileHelper.getInputStreamFromUriString(imageUrl, cordova), null, options);
|
||||
InputStream fileStream = null;
|
||||
try {
|
||||
fileStream = FileHelper.getInputStreamFromUriString(imageUrl, cordova);
|
||||
BitmapFactory.decodeStream(fileStream, null, options);
|
||||
} finally {
|
||||
if (fileStream != null) {
|
||||
try {
|
||||
fileStream.close();
|
||||
} catch (IOException e) {
|
||||
LOG.d(LOG_TAG,"Exception while closing file input stream.");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//CB-2292: WTF? Why is the width null?
|
||||
if(options.outWidth == 0 || options.outHeight == 0)
|
||||
@@ -826,7 +870,19 @@ private String ouputModifiedBitmap(Bitmap bitmap, Uri uri) throws IOException {
|
||||
// Load in the smallest bitmap possible that is closest to the size we want
|
||||
options.inJustDecodeBounds = false;
|
||||
options.inSampleSize = calculateSampleSize(options.outWidth, options.outHeight, this.targetWidth, this.targetHeight);
|
||||
Bitmap unscaledBitmap = BitmapFactory.decodeStream(FileHelper.getInputStreamFromUriString(imageUrl, cordova), null, options);
|
||||
Bitmap unscaledBitmap = null;
|
||||
try {
|
||||
fileStream = FileHelper.getInputStreamFromUriString(imageUrl, cordova);
|
||||
unscaledBitmap = BitmapFactory.decodeStream(fileStream, null, options);
|
||||
} finally {
|
||||
if (fileStream != null) {
|
||||
try {
|
||||
fileStream.close();
|
||||
} catch (IOException e) {
|
||||
LOG.d(LOG_TAG,"Exception while closing file input stream.");
|
||||
}
|
||||
}
|
||||
}
|
||||
if (unscaledBitmap == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
@@ -24,6 +24,7 @@
|
||||
#import <ImageIO/CGImageProperties.h>
|
||||
#import <AssetsLibrary/ALAssetRepresentation.h>
|
||||
#import <AssetsLibrary/AssetsLibrary.h>
|
||||
#import <AVFoundation/AVFoundation.h>
|
||||
#import <ImageIO/CGImageSource.h>
|
||||
#import <ImageIO/CGImageProperties.h>
|
||||
#import <ImageIO/CGImageDestination.h>
|
||||
@@ -141,7 +142,29 @@ static NSString* toBase64(NSData* data) {
|
||||
[weakSelf.commandDelegate sendPluginResult:result callbackId:command.callbackId];
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
// Validate the app has permission to access the camera
|
||||
if (pictureOptions.sourceType == UIImagePickerControllerSourceTypeCamera && [AVCaptureDevice respondsToSelector:@selector(authorizationStatusForMediaType:)]) {
|
||||
AVAuthorizationStatus authStatus = [AVCaptureDevice authorizationStatusForMediaType:AVMediaTypeVideo];
|
||||
if (authStatus == AVAuthorizationStatusDenied ||
|
||||
authStatus == AVAuthorizationStatusRestricted) {
|
||||
// If iOS 8+, offer a link to the Settings app
|
||||
NSString* settingsButton = (&UIApplicationOpenSettingsURLString != NULL)
|
||||
? NSLocalizedString(@"Settings", nil)
|
||||
: nil;
|
||||
|
||||
// Denied; show an alert
|
||||
dispatch_async(dispatch_get_main_queue(), ^{
|
||||
[[[UIAlertView alloc] initWithTitle:[[NSBundle mainBundle]
|
||||
objectForInfoDictionaryKey:@"CFBundleDisplayName"]
|
||||
message:NSLocalizedString(@"Access to the camera has been prohibited; please enable it in the Settings app to continue.", nil)
|
||||
delegate:self
|
||||
cancelButtonTitle:NSLocalizedString(@"OK", nil)
|
||||
otherButtonTitles:settingsButton, nil] show];
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
CDVCameraPicker* cameraPicker = [CDVCameraPicker createFromPictureOptions:pictureOptions];
|
||||
weakSelf.pickerController = cameraPicker;
|
||||
|
||||
@@ -174,6 +197,25 @@ static NSString* toBase64(NSData* data) {
|
||||
}];
|
||||
}
|
||||
|
||||
// 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) {
|
||||
[[UIApplication sharedApplication] openURL:[NSURL URLWithString:UIApplicationOpenSettingsURLString]];
|
||||
}
|
||||
|
||||
// Dismiss the view
|
||||
[[self.pickerController presentingViewController] dismissViewControllerAnimated:YES completion:nil];
|
||||
|
||||
CDVPluginResult* result = [CDVPluginResult resultWithStatus:CDVCommandStatus_ERROR messageAsString:@"has no access to camera"]; // error callback expects string ATM
|
||||
|
||||
[self.commandDelegate sendPluginResult:result callbackId:self.pickerController.callbackId];
|
||||
|
||||
self.hasPendingOperation = NO;
|
||||
self.pickerController = nil;
|
||||
}
|
||||
|
||||
- (void)repositionPopover:(CDVInvokedUrlCommand*)command
|
||||
{
|
||||
NSDictionary* options = [command argumentAtIndex:0 withDefault:nil];
|
||||
|
||||
@@ -25,6 +25,19 @@
|
||||
|
||||
var Camera = require('./Camera');
|
||||
|
||||
|
||||
var getAppData = function () {
|
||||
return Windows.Storage.ApplicationData.current;
|
||||
};
|
||||
var encodeToBase64String = function (buffer) {
|
||||
return Windows.Security.Cryptography.CryptographicBuffer.encodeToBase64String(buffer);
|
||||
};
|
||||
var OptUnique = Windows.Storage.CreationCollisionOption.generateUniqueName;
|
||||
var CapMSType = Windows.Media.Capture.MediaStreamType;
|
||||
var webUIApp = Windows.UI.WebUI.WebUIApplication;
|
||||
var fileIO = Windows.Storage.FileIO;
|
||||
var pickerLocId = Windows.Storage.Pickers.PickerLocationId;
|
||||
|
||||
module.exports = {
|
||||
|
||||
// args will contain :
|
||||
@@ -57,6 +70,9 @@ module.exports = {
|
||||
var windowsVideoContainers = [".avi", ".flv", ".asx", ".asf", ".mov", ".mp4", ".mpg", ".rm", ".srt", ".swf", ".wmv", ".vob"];
|
||||
var windowsPhoneVideoContainers = [".avi", ".3gp", ".3g2", ".wmv", ".3gp", ".3g2", ".mp4", ".m4v"];
|
||||
|
||||
// Default aspect ratio 1.78 (16:9 hd video standard)
|
||||
var DEFAULT_ASPECT_RATIO = '1.8';
|
||||
|
||||
// Resize method
|
||||
function resizeImage(successCallback, errorCallback, file, targetWidth, targetHeight, encodingType) {
|
||||
var tempPhotoFileName = "";
|
||||
@@ -66,11 +82,13 @@ function resizeImage(successCallback, errorCallback, file, targetWidth, targetHe
|
||||
tempPhotoFileName = "camera_cordova_temp_return.jpg";
|
||||
}
|
||||
|
||||
var storageFolder = Windows.Storage.ApplicationData.current.localFolder;
|
||||
var storageFolder = getAppData().localFolder;
|
||||
file.copyAsync(storageFolder, file.name, Windows.Storage.NameCollisionOption.replaceExisting)
|
||||
.then(function (storageFile) { return Windows.Storage.FileIO.readBufferAsync(storageFile); })
|
||||
.then(function (storageFile) {
|
||||
return fileIO.readBufferAsync(storageFile);
|
||||
})
|
||||
.then(function(buffer) {
|
||||
var strBase64 = Windows.Security.Cryptography.CryptographicBuffer.encodeToBase64String(buffer);
|
||||
var strBase64 = encodeToBase64String(buffer);
|
||||
var imageData = "data:" + file.contentType + ";base64," + strBase64;
|
||||
var image = new Image();
|
||||
image.src = imageData;
|
||||
@@ -87,13 +105,13 @@ function resizeImage(successCallback, errorCallback, file, targetWidth, targetHe
|
||||
|
||||
var fileContent = canvas.toDataURL(file.contentType).split(',')[1];
|
||||
|
||||
var storageFolder = Windows.Storage.ApplicationData.current.localFolder;
|
||||
var storageFolder = getAppData().localFolder;
|
||||
|
||||
storageFolder.createFileAsync(tempPhotoFileName, Windows.Storage.CreationCollisionOption.generateUniqueName)
|
||||
storageFolder.createFileAsync(tempPhotoFileName, OptUnique)
|
||||
.then(function (storagefile) {
|
||||
var content = Windows.Security.Cryptography.CryptographicBuffer.decodeFromBase64String(fileContent);
|
||||
storageFileName = storagefile.name;
|
||||
return Windows.Storage.FileIO.writeBufferAsync(storagefile, content);
|
||||
return fileIO.writeBufferAsync(storagefile, content);
|
||||
})
|
||||
.done(function () {
|
||||
successCallback("ms-appdata:///local/" + storageFileName);
|
||||
@@ -108,8 +126,8 @@ function resizeImage(successCallback, errorCallback, file, targetWidth, targetHe
|
||||
|
||||
// Because of asynchronous method, so let the successCallback be called in it.
|
||||
function resizeImageBase64(successCallback, errorCallback, file, targetWidth, targetHeight) {
|
||||
Windows.Storage.FileIO.readBufferAsync(file).done( function(buffer) {
|
||||
var strBase64 = Windows.Security.Cryptography.CryptographicBuffer.encodeToBase64String(buffer);
|
||||
fileIO.readBufferAsync(file).done( function(buffer) {
|
||||
var strBase64 = encodeToBase64String(buffer);
|
||||
var imageData = "data:" + file.contentType + ";base64," + strBase64;
|
||||
|
||||
var image = new Image();
|
||||
@@ -163,7 +181,7 @@ function takePictureFromFileWP(successCallback, errorCallback, args) {
|
||||
var file = eventArgs.files[0];
|
||||
if (!file) {
|
||||
errorCallback("User didn't choose a file.");
|
||||
Windows.UI.WebUI.WebUIApplication.removeEventListener("activated", filePickerActivationHandler);
|
||||
webUIApp.removeEventListener("activated", filePickerActivationHandler);
|
||||
return;
|
||||
}
|
||||
if (destinationType == Camera.DestinationType.FILE_URI || destinationType == Camera.DestinationType.NATIVE_URI) {
|
||||
@@ -171,9 +189,14 @@ function takePictureFromFileWP(successCallback, errorCallback, args) {
|
||||
resizeImage(successCallback, errorCallback, file, targetWidth, targetHeight, encodingType);
|
||||
}
|
||||
else {
|
||||
var storageFolder = Windows.Storage.ApplicationData.current.localFolder;
|
||||
var storageFolder = getAppData().localFolder;
|
||||
file.copyAsync(storageFolder, file.name, Windows.Storage.NameCollisionOption.replaceExisting).done(function (storageFile) {
|
||||
successCallback(URL.createObjectURL(storageFile));
|
||||
if(destinationType == Camera.DestinationType.NATIVE_URI) {
|
||||
successCallback("ms-appdata:///local/" + storageFile.name);
|
||||
}
|
||||
else {
|
||||
successCallback(URL.createObjectURL(storageFile));
|
||||
}
|
||||
}, function () {
|
||||
errorCallback("Can't access localStorage folder.");
|
||||
});
|
||||
@@ -183,31 +206,31 @@ function takePictureFromFileWP(successCallback, errorCallback, args) {
|
||||
if (targetHeight > 0 && targetWidth > 0) {
|
||||
resizeImageBase64(successCallback, errorCallback, file, targetWidth, targetHeight);
|
||||
} else {
|
||||
Windows.Storage.FileIO.readBufferAsync(file).done(function (buffer) {
|
||||
var strBase64 = Windows.Security.Cryptography.CryptographicBuffer.encodeToBase64String(buffer);
|
||||
fileIO.readBufferAsync(file).done(function (buffer) {
|
||||
var strBase64 =encodeToBase64String(buffer);
|
||||
successCallback(strBase64);
|
||||
}, errorCallback);
|
||||
}
|
||||
}
|
||||
Windows.UI.WebUI.WebUIApplication.removeEventListener("activated", filePickerActivationHandler);
|
||||
webUIApp.removeEventListener("activated", filePickerActivationHandler);
|
||||
}
|
||||
};
|
||||
|
||||
var fileOpenPicker = new Windows.Storage.Pickers.FileOpenPicker();
|
||||
if (mediaType == Camera.MediaType.PICTURE) {
|
||||
fileOpenPicker.fileTypeFilter.replaceAll([".png", ".jpg", ".jpeg"]);
|
||||
fileOpenPicker.suggestedStartLocation = Windows.Storage.Pickers.PickerLocationId.picturesLibrary;
|
||||
fileOpenPicker.suggestedStartLocation = pickerLocId.picturesLibrary;
|
||||
}
|
||||
else if (mediaType == Camera.MediaType.VIDEO) {
|
||||
fileOpenPicker.fileTypeFilter.replaceAll(windowsPhoneVideoContainers);
|
||||
fileOpenPicker.suggestedStartLocation = Windows.Storage.Pickers.PickerLocationId.videosLibrary;
|
||||
fileOpenPicker.suggestedStartLocation = pickerLocId.videosLibrary;
|
||||
}
|
||||
else {
|
||||
fileOpenPicker.fileTypeFilter.replaceAll(["*"]);
|
||||
fileOpenPicker.suggestedStartLocation = Windows.Storage.Pickers.PickerLocationId.documentsLibrary;
|
||||
fileOpenPicker.suggestedStartLocation = pickerLocId.documentsLibrary;
|
||||
}
|
||||
|
||||
Windows.UI.WebUI.WebUIApplication.addEventListener("activated", filePickerActivationHandler);
|
||||
webUIApp.addEventListener("activated", filePickerActivationHandler);
|
||||
fileOpenPicker.pickSingleFileAndContinue();
|
||||
}
|
||||
|
||||
@@ -221,15 +244,15 @@ function takePictureFromFileWindows(successCallback, errorCallback, args) {
|
||||
var fileOpenPicker = new Windows.Storage.Pickers.FileOpenPicker();
|
||||
if (mediaType == Camera.MediaType.PICTURE) {
|
||||
fileOpenPicker.fileTypeFilter.replaceAll([".png", ".jpg", ".jpeg"]);
|
||||
fileOpenPicker.suggestedStartLocation = Windows.Storage.Pickers.PickerLocationId.picturesLibrary;
|
||||
fileOpenPicker.suggestedStartLocation = pickerLocId.picturesLibrary;
|
||||
}
|
||||
else if (mediaType == Camera.MediaType.VIDEO) {
|
||||
fileOpenPicker.fileTypeFilter.replaceAll(windowsVideoContainers);
|
||||
fileOpenPicker.suggestedStartLocation = Windows.Storage.Pickers.PickerLocationId.videosLibrary;
|
||||
fileOpenPicker.suggestedStartLocation = pickerLocId.videosLibrary;
|
||||
}
|
||||
else {
|
||||
fileOpenPicker.fileTypeFilter.replaceAll(["*"]);
|
||||
fileOpenPicker.suggestedStartLocation = Windows.Storage.Pickers.PickerLocationId.documentsLibrary;
|
||||
fileOpenPicker.suggestedStartLocation = pickerLocId.documentsLibrary;
|
||||
}
|
||||
|
||||
fileOpenPicker.pickSingleFileAsync().done(function (file) {
|
||||
@@ -242,7 +265,7 @@ function takePictureFromFileWindows(successCallback, errorCallback, args) {
|
||||
resizeImage(successCallback, errorCallback, file, targetWidth, targetHeight, encodingType);
|
||||
}
|
||||
else {
|
||||
var storageFolder = Windows.Storage.ApplicationData.current.localFolder;
|
||||
var storageFolder = getAppData().localFolder;
|
||||
file.copyAsync(storageFolder, file.name, Windows.Storage.NameCollisionOption.replaceExisting).done(function (storageFile) {
|
||||
successCallback(URL.createObjectURL(storageFile));
|
||||
}, function () {
|
||||
@@ -254,8 +277,8 @@ function takePictureFromFileWindows(successCallback, errorCallback, args) {
|
||||
if (targetHeight > 0 && targetWidth > 0) {
|
||||
resizeImageBase64(successCallback, errorCallback, file, targetWidth, targetHeight);
|
||||
} else {
|
||||
Windows.Storage.FileIO.readBufferAsync(file).done(function (buffer) {
|
||||
var strBase64 = Windows.Security.Cryptography.CryptographicBuffer.encodeToBase64String(buffer);
|
||||
fileIO.readBufferAsync(file).done(function (buffer) {
|
||||
var strBase64 =encodeToBase64String(buffer);
|
||||
successCallback(strBase64);
|
||||
}, errorCallback);
|
||||
}
|
||||
@@ -287,18 +310,8 @@ function takePictureFromCameraWP(successCallback, errorCallback, args) {
|
||||
captureCancelButton = null,
|
||||
capture = null,
|
||||
captureSettings = null,
|
||||
CaptureNS = Windows.Media.Capture;
|
||||
|
||||
function cameraPreviewOrientation(arg) {
|
||||
// Rotate the cam since WP8.1 MediaCapture outputs video stream rotated 90° CCW
|
||||
if (screen.msOrientation === "portrait-primary" || screen.msOrientation === "portrait-secondary") {
|
||||
capture.setPreviewRotation(Windows.Media.Capture.VideoRotation.clockwise90Degrees);
|
||||
} else if (screen.msOrientation === "landscape-secondary") {
|
||||
capture.setPreviewRotation(Windows.Media.Capture.VideoRotation.clockwise180Degrees);
|
||||
} else {
|
||||
capture.setPreviewRotation(Windows.Media.Capture.VideoRotation.none);
|
||||
}
|
||||
}
|
||||
CaptureNS = Windows.Media.Capture,
|
||||
sensor = null;
|
||||
|
||||
var createCameraUI = function () {
|
||||
// Create fullscreen preview
|
||||
@@ -322,9 +335,10 @@ function takePictureFromCameraWP(successCallback, errorCallback, args) {
|
||||
var startCameraPreview = function () {
|
||||
// Search for available camera devices
|
||||
// This is necessary to detect which camera (front or back) we should use
|
||||
var expectedPanel = cameraDirection === 1 ? Windows.Devices.Enumeration.Panel.front : Windows.Devices.Enumeration.Panel.back;
|
||||
Windows.Devices.Enumeration.DeviceInformation.findAllAsync(Windows.Devices.Enumeration.DeviceClass.videoCapture)
|
||||
.done(function (devices) {
|
||||
var DeviceEnum = Windows.Devices.Enumeration;
|
||||
var expectedPanel = cameraDirection === 1 ? DeviceEnum.Panel.front : DeviceEnum.Panel.back;
|
||||
|
||||
DeviceEnum.DeviceInformation.findAllAsync(DeviceEnum.DeviceClass.videoCapture).then(function (devices) {
|
||||
if (devices.length <= 0) {
|
||||
destroyCameraPreview();
|
||||
errorCallback('Camera not found');
|
||||
@@ -337,32 +351,63 @@ function takePictureFromCameraWP(successCallback, errorCallback, args) {
|
||||
}
|
||||
});
|
||||
|
||||
capture.initializeAsync(captureSettings).done(function () {
|
||||
// msdn.microsoft.com/en-us/library/windows/apps/hh452807.aspx
|
||||
capturePreview.msZoom = true;
|
||||
capturePreview.src = URL.createObjectURL(capture);
|
||||
capturePreview.play();
|
||||
captureSettings.photoCaptureSource = Windows.Media.Capture.PhotoCaptureSource.photo;
|
||||
|
||||
// Insert preview frame and controls into page
|
||||
document.body.appendChild(capturePreview);
|
||||
document.body.appendChild(captureCancelButton);
|
||||
return capture.initializeAsync(captureSettings);
|
||||
}).then(function () {
|
||||
// msdn.microsoft.com/en-us/library/windows/apps/hh452807.aspx
|
||||
capturePreview.msZoom = true;
|
||||
capturePreview.src = URL.createObjectURL(capture);
|
||||
capturePreview.play();
|
||||
|
||||
// Bind events to controls
|
||||
window.addEventListener('deviceorientation', cameraPreviewOrientation, false);
|
||||
capturePreview.addEventListener('click', captureAction);
|
||||
captureCancelButton.addEventListener('click', function () {
|
||||
destroyCameraPreview();
|
||||
errorCallback('Cancelled');
|
||||
}, false);
|
||||
}, function (err) {
|
||||
// Bind events to controls
|
||||
sensor = Windows.Devices.Sensors.SimpleOrientationSensor.getDefault();
|
||||
if (sensor !== null) {
|
||||
sensor.addEventListener("orientationchanged", onOrientationChange);
|
||||
}
|
||||
capturePreview.addEventListener('click', captureAction);
|
||||
captureCancelButton.addEventListener('click', function () {
|
||||
destroyCameraPreview();
|
||||
errorCallback('Camera intitialization error ' + err);
|
||||
});
|
||||
}, errorCallback);
|
||||
errorCallback('Cancelled');
|
||||
}, false);
|
||||
|
||||
// Change default orientation
|
||||
if (sensor) {
|
||||
setPreviewRotation(sensor.getCurrentOrientation());
|
||||
} else {
|
||||
setPreviewRotation(Windows.Graphics.Display.DisplayInformation.getForCurrentView().currentOrientation);
|
||||
}
|
||||
|
||||
// Get available aspect ratios
|
||||
var aspectRatios = getAspectRatios(capture);
|
||||
|
||||
// Couldn't find a good ratio
|
||||
if (aspectRatios.length === 0) {
|
||||
destroyCameraPreview();
|
||||
errorCallback('There\'s not a good aspect ratio available');
|
||||
return;
|
||||
}
|
||||
|
||||
// Insert preview frame and controls into page
|
||||
document.body.appendChild(capturePreview);
|
||||
document.body.appendChild(captureCancelButton);
|
||||
|
||||
if (aspectRatios.indexOf(DEFAULT_ASPECT_RATIO) > -1) {
|
||||
return setAspectRatio(capture, DEFAULT_ASPECT_RATIO);
|
||||
} else {
|
||||
// Doesn't support 16:9 - pick next best
|
||||
return setAspectRatio(capture, aspectRatios[0]);
|
||||
}
|
||||
}).done(null, function (err) {
|
||||
destroyCameraPreview();
|
||||
errorCallback('Camera intitialization error ' + err);
|
||||
});
|
||||
};
|
||||
|
||||
var destroyCameraPreview = function () {
|
||||
window.removeEventListener('deviceorientation', cameraPreviewOrientation, false);
|
||||
if (sensor !== null) {
|
||||
sensor.removeEventListener('orientationchanged', onOrientationChange);
|
||||
}
|
||||
capturePreview.pause();
|
||||
capturePreview.src = null;
|
||||
[capturePreview, captureCancelButton].forEach(function(elem) {
|
||||
@@ -380,8 +425,7 @@ function takePictureFromCameraWP(successCallback, errorCallback, args) {
|
||||
|
||||
var encodingProperties,
|
||||
fileName,
|
||||
generateUniqueCollisionOption = Windows.Storage.CreationCollisionOption.generateUniqueName,
|
||||
tempFolder = Windows.Storage.ApplicationData.current.temporaryFolder;
|
||||
tempFolder = getAppData().temporaryFolder;
|
||||
|
||||
if (encodingType == Camera.EncodingType.PNG) {
|
||||
fileName = 'photo.png';
|
||||
@@ -391,118 +435,171 @@ function takePictureFromCameraWP(successCallback, errorCallback, args) {
|
||||
encodingProperties = Windows.Media.MediaProperties.ImageEncodingProperties.createJpeg();
|
||||
}
|
||||
|
||||
tempFolder.createFileAsync(fileName, generateUniqueCollisionOption)
|
||||
tempFolder.createFileAsync(fileName, OptUnique)
|
||||
.then(function(tempCapturedFile) {
|
||||
return new WinJS.Promise(function (complete) {
|
||||
var imgStream = new Windows.Storage.Streams.InMemoryRandomAccessStream();
|
||||
capture.capturePhotoToStreamAsync(encodingProperties, imgStream)
|
||||
var photoStream = new Windows.Storage.Streams.InMemoryRandomAccessStream();
|
||||
var finalStream = new Windows.Storage.Streams.InMemoryRandomAccessStream();
|
||||
capture.capturePhotoToStreamAsync(encodingProperties, photoStream)
|
||||
.then(function() {
|
||||
return Windows.Graphics.Imaging.BitmapDecoder.createAsync(imgStream);
|
||||
return Windows.Graphics.Imaging.BitmapDecoder.createAsync(photoStream);
|
||||
})
|
||||
.then(function(dec) {
|
||||
return Windows.Graphics.Imaging.BitmapEncoder.createForTranscodingAsync(imgStream, dec);
|
||||
finalStream.size = 0; // BitmapEncoder requires the output stream to be empty
|
||||
return Windows.Graphics.Imaging.BitmapEncoder.createForTranscodingAsync(finalStream, dec);
|
||||
})
|
||||
.then(function(enc) {
|
||||
// We need to rotate the photo 90° CW because by default wp8.1 takes 90° CCW rotated photos.
|
||||
enc.bitmapTransform.rotation = Windows.Graphics.Imaging.BitmapRotation.clockwise90Degrees;
|
||||
// We need to rotate the photo wrt sensor orientation
|
||||
enc.bitmapTransform.rotation = orientationToRotation(sensor.getCurrentOrientation());
|
||||
return enc.flushAsync();
|
||||
})
|
||||
.then(function() {
|
||||
return tempCapturedFile.openAsync(Windows.Storage.FileAccessMode.readWrite);
|
||||
})
|
||||
.then(function(fileStream) {
|
||||
return Windows.Storage.Streams.RandomAccessStream.copyAsync(imgStream, fileStream);
|
||||
return Windows.Storage.Streams.RandomAccessStream.copyAsync(finalStream, fileStream);
|
||||
})
|
||||
.done(function() {
|
||||
imgStream.close();
|
||||
photoStream.close();
|
||||
finalStream.close();
|
||||
complete(tempCapturedFile);
|
||||
}, function() {
|
||||
imgStream.close();
|
||||
photoStream.close();
|
||||
finalStream.close();
|
||||
throw new Error("An error has occured while capturing the photo.");
|
||||
});
|
||||
})
|
||||
});
|
||||
})
|
||||
.done(function(capturedFile) {
|
||||
destroyCameraPreview();
|
||||
|
||||
// success callback for capture operation
|
||||
var success = function(capturedPhoto) {
|
||||
if (destinationType == Camera.DestinationType.FILE_URI || destinationType == Camera.DestinationType.NATIVE_URI) {
|
||||
if (targetHeight > 0 && targetWidth > 0) {
|
||||
resizeImage(successCallback, errorCallback, capturedPhoto, targetWidth, targetHeight, encodingType);
|
||||
} else {
|
||||
capturedPhoto.copyAsync(Windows.Storage.ApplicationData.current.localFolder, capturedPhoto.name, generateUniqueCollisionOption).done(function(copiedfile) {
|
||||
successCallback("ms-appdata:///local/" + copiedfile.name);
|
||||
}, errorCallback);
|
||||
}
|
||||
} else {
|
||||
if (targetHeight > 0 && targetWidth > 0) {
|
||||
resizeImageBase64(successCallback, errorCallback, capturedPhoto, targetWidth, targetHeight);
|
||||
} else {
|
||||
Windows.Storage.FileIO.readBufferAsync(capturedPhoto).done(function(buffer) {
|
||||
var strBase64 = Windows.Security.Cryptography.CryptographicBuffer.encodeToBase64String(buffer);
|
||||
capturedPhoto.deleteAsync().done(function() {
|
||||
successCallback(strBase64);
|
||||
}, function(err) {
|
||||
errorCallback(err);
|
||||
});
|
||||
}, errorCallback);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
if (!saveToPhotoAlbum) {
|
||||
success(capturedFile);
|
||||
return;
|
||||
}
|
||||
|
||||
/*
|
||||
Need to add and remove an event listener to catch activation state
|
||||
Using FileSavePicker will suspend the app and it's required to catch the pickSaveFileContinuation
|
||||
https://msdn.microsoft.com/en-us/library/windows/apps/xaml/dn631755.aspx
|
||||
*/
|
||||
var cameraActivationHandler = function(eventArgs) {
|
||||
if (eventArgs.kind === Windows.ApplicationModel.Activation.ActivationKind.pickSaveFileContinuation) {
|
||||
var file = eventArgs.file;
|
||||
if (file) {
|
||||
// Prevent updates to the remote version of the file until we're done
|
||||
Windows.Storage.CachedFileManager.deferUpdates(file);
|
||||
capturedFile.moveAndReplaceAsync(file)
|
||||
.then(function() {
|
||||
// Let Windows know that we're finished changing the file so
|
||||
// the other app can update the remote version of the file.
|
||||
return Windows.Storage.CachedFileManager.completeUpdatesAsync(file);
|
||||
})
|
||||
.done(function(updateStatus) {
|
||||
if (updateStatus === Windows.Storage.Provider.FileUpdateStatus.complete) {
|
||||
success(capturedFile);
|
||||
} else {
|
||||
errorCallback();
|
||||
}
|
||||
}, errorCallback);
|
||||
} else {
|
||||
errorCallback();
|
||||
}
|
||||
Windows.UI.WebUI.WebUIApplication.removeEventListener("activated", cameraActivationHandler);
|
||||
}
|
||||
};
|
||||
|
||||
var savePicker = new Windows.Storage.Pickers.FileSavePicker();
|
||||
savePicker.suggestedStartLocation = Windows.Storage.Pickers.PickerLocationId.picturesLibrary;
|
||||
if (encodingType === Camera.EncodingType.PNG) {
|
||||
savePicker.fileTypeChoices.insert("PNG", [".png"]);
|
||||
} else {
|
||||
savePicker.fileTypeChoices.insert("JPEG", [".jpg"]);
|
||||
}
|
||||
savePicker.suggestedFileName = fileName;
|
||||
Windows.UI.WebUI.WebUIApplication.addEventListener("activated", cameraActivationHandler);
|
||||
savePicker.pickSaveFileAndContinue();
|
||||
savePhoto(capturedFile, {
|
||||
destinationType: destinationType,
|
||||
targetHeight: targetHeight,
|
||||
targetWidth: targetWidth,
|
||||
encodingType: encodingType,
|
||||
saveToPhotoAlbum: saveToPhotoAlbum
|
||||
}, successCallback, errorCallback);
|
||||
}, function(err) {
|
||||
destroyCameraPreview();
|
||||
errorCallback(err);
|
||||
});
|
||||
};
|
||||
|
||||
var getAspectRatios = function (capture) {
|
||||
var videoDeviceController = capture.videoDeviceController;
|
||||
var photoAspectRatios = videoDeviceController.getAvailableMediaStreamProperties(CapMSType.photo).map(function (element) {
|
||||
return (element.width / element.height).toFixed(1);
|
||||
}).filter(function (element, index, array) { return (index === array.indexOf(element)); });
|
||||
|
||||
var videoAspectRatios = videoDeviceController.getAvailableMediaStreamProperties(CapMSType.videoRecord).map(function (element) {
|
||||
return (element.width / element.height).toFixed(1);
|
||||
}).filter(function (element, index, array) { return (index === array.indexOf(element)); });
|
||||
|
||||
var videoPreviewAspectRatios = videoDeviceController.getAvailableMediaStreamProperties(CapMSType.videoPreview).map(function (element) {
|
||||
return (element.width / element.height).toFixed(1);
|
||||
}).filter(function (element, index, array) { return (index === array.indexOf(element)); });
|
||||
|
||||
var allAspectRatios = [].concat(photoAspectRatios, videoAspectRatios, videoPreviewAspectRatios);
|
||||
|
||||
var aspectObj = allAspectRatios.reduce(function (map, item) {
|
||||
if (!map[item]) {
|
||||
map[item] = 0;
|
||||
}
|
||||
map[item]++;
|
||||
return map;
|
||||
}, {});
|
||||
|
||||
return Object.keys(aspectObj).filter(function (k) {
|
||||
return aspectObj[k] === 3;
|
||||
});
|
||||
};
|
||||
|
||||
var setAspectRatio = function (capture, aspect) {
|
||||
// Max photo resolution with desired aspect ratio
|
||||
var videoDeviceController = capture.videoDeviceController;
|
||||
var photoResolution = videoDeviceController.getAvailableMediaStreamProperties(CapMSType.photo)
|
||||
.filter(function (elem) {
|
||||
return ((elem.width / elem.height).toFixed(1) === aspect);
|
||||
})
|
||||
.reduce(function (prop1, prop2) {
|
||||
return (prop1.width * prop1.height) > (prop2.width * prop2.height) ? prop1 : prop2;
|
||||
});
|
||||
|
||||
// Max video resolution with desired aspect ratio
|
||||
var videoRecordResolution = videoDeviceController.getAvailableMediaStreamProperties(CapMSType.videoRecord)
|
||||
.filter(function (elem) {
|
||||
return ((elem.width / elem.height).toFixed(1) === aspect);
|
||||
})
|
||||
.reduce(function (prop1, prop2) {
|
||||
return (prop1.width * prop1.height) > (prop2.width * prop2.height) ? prop1 : prop2;
|
||||
});
|
||||
|
||||
// Max video preview resolution with desired aspect ratio
|
||||
var videoPreviewResolution = videoDeviceController.getAvailableMediaStreamProperties(CapMSType.videoPreview)
|
||||
.filter(function (elem) {
|
||||
return ((elem.width / elem.height).toFixed(1) === aspect);
|
||||
})
|
||||
.reduce(function (prop1, prop2) {
|
||||
return (prop1.width * prop1.height) > (prop2.width * prop2.height) ? prop1 : prop2;
|
||||
});
|
||||
|
||||
return videoDeviceController.setMediaStreamPropertiesAsync(CapMSType.photo, photoResolution)
|
||||
.then(function () {
|
||||
return videoDeviceController.setMediaStreamPropertiesAsync(CapMSType.videoPreview, videoPreviewResolution);
|
||||
})
|
||||
.then(function () {
|
||||
return videoDeviceController.setMediaStreamPropertiesAsync(CapMSType.videoRecord, videoRecordResolution);
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* When the phone orientation change, get the event and change camera preview rotation
|
||||
* @param {Object} e - SimpleOrientationSensorOrientationChangedEventArgs
|
||||
*/
|
||||
var onOrientationChange = function (e) {
|
||||
setPreviewRotation(e.orientation);
|
||||
};
|
||||
|
||||
/**
|
||||
* Converts SimpleOrientation to a VideoRotation to remove difference between camera sensor orientation
|
||||
* and video orientation
|
||||
* @param {number} orientation - Windows.Devices.Sensors.SimpleOrientation
|
||||
* @return {number} - Windows.Media.Capture.VideoRotation
|
||||
*/
|
||||
var orientationToRotation = function (orientation) {
|
||||
// VideoRotation enumerable and BitmapRotation enumerable have the same values
|
||||
// https://msdn.microsoft.com/en-us/library/windows/apps/windows.media.capture.videorotation.aspx
|
||||
// https://msdn.microsoft.com/en-us/library/windows/apps/windows.graphics.imaging.bitmaprotation.aspx
|
||||
|
||||
switch (orientation) {
|
||||
// portrait
|
||||
case Windows.Devices.Sensors.SimpleOrientation.notRotated:
|
||||
return Windows.Media.Capture.VideoRotation.clockwise90Degrees;
|
||||
// landscape
|
||||
case Windows.Devices.Sensors.SimpleOrientation.rotated90DegreesCounterclockwise:
|
||||
return Windows.Media.Capture.VideoRotation.none;
|
||||
// portrait-flipped (not supported by WinPhone Apps)
|
||||
case Windows.Devices.Sensors.SimpleOrientation.rotated180DegreesCounterclockwise:
|
||||
// Falling back to portrait default
|
||||
return Windows.Media.Capture.VideoRotation.clockwise90Degrees;
|
||||
// landscape-flipped
|
||||
case Windows.Devices.Sensors.SimpleOrientation.rotated270DegreesCounterclockwise:
|
||||
return Windows.Media.Capture.VideoRotation.clockwise180Degrees;
|
||||
// faceup & facedown
|
||||
default:
|
||||
// Falling back to portrait default
|
||||
return Windows.Media.Capture.VideoRotation.clockwise90Degrees;
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Rotates the current MediaCapture's video
|
||||
* @param {number} orientation - Windows.Devices.Sensors.SimpleOrientation
|
||||
*/
|
||||
var setPreviewRotation = function(orientation) {
|
||||
capture.setPreviewRotation(orientationToRotation(orientation));
|
||||
};
|
||||
|
||||
try {
|
||||
createCameraUI();
|
||||
startCameraPreview();
|
||||
@@ -518,74 +615,116 @@ function takePictureFromCameraWindows(successCallback, errorCallback, args) {
|
||||
encodingType = args[5],
|
||||
allowCrop = !!args[7],
|
||||
saveToPhotoAlbum = args[9],
|
||||
cameraCaptureUI = new Windows.Media.Capture.CameraCaptureUI();
|
||||
WMCapture = Windows.Media.Capture,
|
||||
cameraCaptureUI = new WMCapture.CameraCaptureUI();
|
||||
|
||||
cameraCaptureUI.photoSettings.allowCropping = allowCrop;
|
||||
|
||||
if (encodingType == Camera.EncodingType.PNG) {
|
||||
cameraCaptureUI.photoSettings.format = Windows.Media.Capture.CameraCaptureUIPhotoFormat.png;
|
||||
cameraCaptureUI.photoSettings.format = WMCapture.CameraCaptureUIPhotoFormat.png;
|
||||
} else {
|
||||
cameraCaptureUI.photoSettings.format = Windows.Media.Capture.CameraCaptureUIPhotoFormat.jpeg;
|
||||
cameraCaptureUI.photoSettings.format = WMCapture.CameraCaptureUIPhotoFormat.jpeg;
|
||||
}
|
||||
|
||||
// decide which max pixels should be supported by targetWidth or targetHeight.
|
||||
if (targetWidth >= 1280 || targetHeight >= 960) {
|
||||
cameraCaptureUI.photoSettings.maxResolution = Windows.Media.Capture.CameraCaptureUIMaxPhotoResolution.large3M;
|
||||
} else if (targetWidth >= 1024 || targetHeight >= 768) {
|
||||
cameraCaptureUI.photoSettings.maxResolution = Windows.Media.Capture.CameraCaptureUIMaxPhotoResolution.mediumXga;
|
||||
} else if (targetWidth >= 800 || targetHeight >= 600) {
|
||||
cameraCaptureUI.photoSettings.maxResolution = Windows.Media.Capture.CameraCaptureUIMaxPhotoResolution.mediumXga;
|
||||
} else if (targetWidth >= 640 || targetHeight >= 480) {
|
||||
cameraCaptureUI.photoSettings.maxResolution = Windows.Media.Capture.CameraCaptureUIMaxPhotoResolution.smallVga;
|
||||
} else if (targetWidth >= 320 || targetHeight >= 240) {
|
||||
cameraCaptureUI.photoSettings.maxResolution = Windows.Media.Capture.CameraCaptureUIMaxPhotoResolution.verySmallQvga;
|
||||
} else {
|
||||
cameraCaptureUI.photoSettings.maxResolution = Windows.Media.Capture.CameraCaptureUIMaxPhotoResolution.highestAvailable;
|
||||
var maxRes = null;
|
||||
var UIMaxRes = WMCapture.CameraCaptureUIMaxPhotoResolution;
|
||||
switch (true) {
|
||||
case (targetWidth >= 1280 || targetHeight >= 960) :
|
||||
cameraCaptureUI.photoSettings.maxResolution = UIMaxRes.large3M;
|
||||
break;
|
||||
case (targetWidth >= 1024 || targetHeight >= 768) :
|
||||
maxRes = UIMaxRes.mediumXga;
|
||||
break;
|
||||
case (targetWidth >= 800 || targetHeight >= 600) :
|
||||
maxRes = UIMaxRes.mediumXga;
|
||||
break;
|
||||
case (targetWidth >= 640 || targetHeight >= 480) :
|
||||
maxRes = UIMaxRes.smallVga;
|
||||
break;
|
||||
case (targetWidth >= 320 || targetHeight >= 240) :
|
||||
maxRes = UIMaxRes.verySmallQvga;
|
||||
break;
|
||||
default :
|
||||
maxRes = UIMaxRes.highestAvailable;
|
||||
}
|
||||
|
||||
cameraCaptureUI.captureFileAsync(Windows.Media.Capture.CameraCaptureUIMode.photo).done(function(picture) {
|
||||
cameraCaptureUI.photoSettings.maxResolution = maxRes;
|
||||
|
||||
cameraCaptureUI.captureFileAsync(WMCapture.CameraCaptureUIMode.photo).done(function(picture) {
|
||||
if (!picture) {
|
||||
errorCallback("User didn't capture a photo.");
|
||||
return;
|
||||
}
|
||||
|
||||
// save to photo album successCallback
|
||||
var success = function() {
|
||||
if (destinationType == Camera.DestinationType.FILE_URI) {
|
||||
if (targetHeight > 0 && targetWidth > 0) {
|
||||
resizeImage(successCallback, errorCallback, picture, targetWidth, targetHeight, encodingType);
|
||||
} else {
|
||||
var storageFolder = Windows.Storage.ApplicationData.current.localFolder;
|
||||
picture.copyAsync(storageFolder, picture.name, Windows.Storage.NameCollisionOption.replaceExisting).done(function(storageFile) {
|
||||
successCallback("ms-appdata:///local/" + storageFile.name);
|
||||
}, errorCallback);
|
||||
}
|
||||
savePhoto(picture, {
|
||||
destinationType: destinationType,
|
||||
targetHeight: targetHeight,
|
||||
targetWidth: targetWidth,
|
||||
encodingType: encodingType,
|
||||
saveToPhotoAlbum: saveToPhotoAlbum
|
||||
}, successCallback, errorCallback);
|
||||
}, function() {
|
||||
errorCallback("Fail to capture a photo.");
|
||||
});
|
||||
}
|
||||
|
||||
function savePhoto(picture, options, successCallback, errorCallback) {
|
||||
// success callback for capture operation
|
||||
var success = function(picture) {
|
||||
if (options.destinationType == Camera.DestinationType.FILE_URI || options.destinationType == Camera.DestinationType.NATIVE_URI) {
|
||||
if (options.targetHeight > 0 && options.targetWidth > 0) {
|
||||
resizeImage(successCallback, errorCallback, picture, options.targetWidth, options.targetHeight, options.encodingType);
|
||||
} else {
|
||||
if (targetHeight > 0 && targetWidth > 0) {
|
||||
resizeImageBase64(successCallback, errorCallback, picture, targetWidth, targetHeight);
|
||||
} else {
|
||||
Windows.Storage.FileIO.readBufferAsync(picture).done(function(buffer) {
|
||||
var strBase64 = Windows.Security.Cryptography.CryptographicBuffer.encodeToBase64String(buffer);
|
||||
picture.copyAsync(getAppData().localFolder, picture.name, OptUnique).done(function (copiedFile) {
|
||||
successCallback("ms-appdata:///local/" + copiedFile.name);
|
||||
},errorCallback);
|
||||
}
|
||||
} else {
|
||||
if (options.targetHeight > 0 && options.targetWidth > 0) {
|
||||
resizeImageBase64(successCallback, errorCallback, picture, options.targetWidth, options.targetHeight);
|
||||
} else {
|
||||
fileIO.readBufferAsync(picture).done(function(buffer) {
|
||||
var strBase64 =encodeToBase64String(buffer);
|
||||
picture.deleteAsync().done(function() {
|
||||
successCallback(strBase64);
|
||||
}, function(err) {
|
||||
errorCallback(err);
|
||||
});
|
||||
}, errorCallback);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
if (!options.saveToPhotoAlbum) {
|
||||
success(picture);
|
||||
return;
|
||||
} else {
|
||||
var savePicker = new Windows.Storage.Pickers.FileSavePicker();
|
||||
var saveFile = function(file) {
|
||||
if (file) {
|
||||
// Prevent updates to the remote version of the file until we're done
|
||||
Windows.Storage.CachedFileManager.deferUpdates(file);
|
||||
picture.moveAndReplaceAsync(file)
|
||||
.then(function() {
|
||||
// Let Windows know that we're finished changing the file so
|
||||
// the other app can update the remote version of the file.
|
||||
return Windows.Storage.CachedFileManager.completeUpdatesAsync(file);
|
||||
})
|
||||
.done(function(updateStatus) {
|
||||
if (updateStatus === Windows.Storage.Provider.FileUpdateStatus.complete) {
|
||||
success(picture);
|
||||
} else {
|
||||
errorCallback("File update status is not complete.");
|
||||
}
|
||||
}, errorCallback);
|
||||
}
|
||||
} else {
|
||||
errorCallback("Failed to select a file.");
|
||||
}
|
||||
};
|
||||
savePicker.suggestedStartLocation = pickerLocId.picturesLibrary;
|
||||
|
||||
// save to photo album errorCallback
|
||||
var fail = function() {
|
||||
//errorCallback("FileError, code:" + fileError.code);
|
||||
errorCallback("Save fail.");
|
||||
};
|
||||
|
||||
if (!saveToPhotoAlbum) {
|
||||
success();
|
||||
return;
|
||||
}
|
||||
|
||||
var savePicker = new Windows.Storage.Pickers.FileSavePicker();
|
||||
|
||||
savePicker.suggestedStartLocation = Windows.Storage.Pickers.PickerLocationId.picturesLibrary;
|
||||
if (encodingType === Camera.EncodingType.PNG) {
|
||||
if (options.encodingType === Camera.EncodingType.PNG) {
|
||||
savePicker.fileTypeChoices.insert("PNG", [".png"]);
|
||||
savePicker.suggestedFileName = "photo.png";
|
||||
} else {
|
||||
@@ -593,31 +732,27 @@ function takePictureFromCameraWindows(successCallback, errorCallback, args) {
|
||||
savePicker.suggestedFileName = "photo.jpg";
|
||||
}
|
||||
|
||||
savePicker.pickSaveFileAsync()
|
||||
.done(function(file) {
|
||||
if (file) {
|
||||
// Prevent updates to the remote version of the file until we're done
|
||||
Windows.Storage.CachedFileManager.deferUpdates(file);
|
||||
picture.moveAndReplaceAsync(file)
|
||||
.then(function() {
|
||||
// Let Windows know that we're finished changing the file so
|
||||
// the other app can update the remote version of the file.
|
||||
return Windows.Storage.CachedFileManager.completeUpdatesAsync(file);
|
||||
})
|
||||
.done(function(updateStatus) {
|
||||
if (updateStatus === Windows.Storage.Provider.FileUpdateStatus.complete) {
|
||||
success();
|
||||
} else {
|
||||
fail();
|
||||
}
|
||||
}, fail);
|
||||
} else {
|
||||
fail();
|
||||
// If Windows Phone 8.1 use pickSaveFileAndContinue()
|
||||
if (navigator.appVersion.indexOf('Windows Phone 8.1') >= 0) {
|
||||
/*
|
||||
Need to add and remove an event listener to catch activation state
|
||||
Using FileSavePicker will suspend the app and it's required to catch the pickSaveFileContinuation
|
||||
https://msdn.microsoft.com/en-us/library/windows/apps/xaml/dn631755.aspx
|
||||
*/
|
||||
var fileSaveHandler = function(eventArgs) {
|
||||
if (eventArgs.kind === Windows.ApplicationModel.Activation.ActivationKind.pickSaveFileContinuation) {
|
||||
var file = eventArgs.file;
|
||||
saveFile(file);
|
||||
webUIApp.removeEventListener("activated", fileSaveHandler);
|
||||
}
|
||||
}, fail);
|
||||
}, function() {
|
||||
errorCallback("Fail to capture a photo.");
|
||||
});
|
||||
};
|
||||
webUIApp.addEventListener("activated", fileSaveHandler);
|
||||
savePicker.pickSaveFileAndContinue();
|
||||
} else {
|
||||
savePicker.pickSaveFileAsync()
|
||||
.done(saveFile, errorCallback);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
require("cordova/exec/proxy").add("Camera",module.exports);
|
||||
|
||||
@@ -65,7 +65,9 @@
|
||||
|
||||
// No arguments, check whether the defaults are set
|
||||
args = @[];
|
||||
options = [CDVPictureOptions createFromTakePictureArguments:args];
|
||||
CDVInvokedUrlCommand* command = [[CDVInvokedUrlCommand alloc] initWithArguments:args callbackId:@"dummy" className:@"myclassname" methodName:@"mymethodname"];
|
||||
|
||||
options = [CDVPictureOptions createFromTakePictureArguments:command];
|
||||
|
||||
XCTAssertEqual([options.quality intValue], 50);
|
||||
XCTAssertEqual(options.destinationType, (int)DestinationTypeFileUri);
|
||||
@@ -99,7 +101,9 @@
|
||||
popoverOptions,
|
||||
@(UIImagePickerControllerCameraDeviceFront),
|
||||
];
|
||||
options = [CDVPictureOptions createFromTakePictureArguments:args];
|
||||
|
||||
command = [[CDVInvokedUrlCommand alloc] initWithArguments:args callbackId:@"dummy" className:@"myclassname" methodName:@"mymethodname"];
|
||||
options = [CDVPictureOptions createFromTakePictureArguments:command];
|
||||
|
||||
XCTAssertEqual([options.quality intValue], 49);
|
||||
XCTAssertEqual(options.destinationType, (int)DestinationTypeDataUrl);
|
||||
@@ -141,7 +145,9 @@
|
||||
popoverOptions,
|
||||
@(UIImagePickerControllerCameraDeviceFront),
|
||||
];
|
||||
pictureOptions = [CDVPictureOptions createFromTakePictureArguments:args];
|
||||
|
||||
CDVInvokedUrlCommand* command = [[CDVInvokedUrlCommand alloc] initWithArguments:args callbackId:@"dummy" className:@"myclassname" methodName:@"mymethodname"];
|
||||
pictureOptions = [CDVPictureOptions createFromTakePictureArguments:command];
|
||||
|
||||
if ([UIImagePickerController isSourceTypeAvailable:pictureOptions.sourceType]) {
|
||||
picker = [CDVCameraPicker createFromPictureOptions:pictureOptions];
|
||||
@@ -170,7 +176,9 @@
|
||||
popoverOptions,
|
||||
@(UIImagePickerControllerCameraDeviceFront),
|
||||
];
|
||||
pictureOptions = [CDVPictureOptions createFromTakePictureArguments:args];
|
||||
|
||||
command = [[CDVInvokedUrlCommand alloc] initWithArguments:args callbackId:@"dummy" className:@"myclassname" methodName:@"mymethodname"];
|
||||
pictureOptions = [CDVPictureOptions createFromTakePictureArguments:command];
|
||||
|
||||
if ([UIImagePickerController isSourceTypeAvailable:pictureOptions.sourceType]) {
|
||||
picker = [CDVCameraPicker createFromPictureOptions:pictureOptions];
|
||||
@@ -198,7 +206,9 @@
|
||||
popoverOptions,
|
||||
@(UIImagePickerControllerCameraDeviceFront),
|
||||
];
|
||||
pictureOptions = [CDVPictureOptions createFromTakePictureArguments:args];
|
||||
|
||||
command = [[CDVInvokedUrlCommand alloc] initWithArguments:args callbackId:@"dummy" className:@"myclassname" methodName:@"mymethodname"];
|
||||
pictureOptions = [CDVPictureOptions createFromTakePictureArguments:command];
|
||||
|
||||
if ([UIImagePickerController isSourceTypeAvailable:pictureOptions.sourceType]) {
|
||||
picker = [CDVCameraPicker createFromPictureOptions:pictureOptions];
|
||||
|
||||
@@ -1,5 +1,25 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||
<!--
|
||||
#
|
||||
# Licensed to the Apache Software Foundation (ASF) under one
|
||||
# or more contributor license agreements. See the NOTICE file
|
||||
# distributed with this work for additional information
|
||||
# regarding copyright ownership. The ASF licenses this file
|
||||
# to you under the Apache License, Version 2.0 (the
|
||||
# "License"); you may not use this file except in compliance
|
||||
# with the License. You may obtain a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing,
|
||||
# software distributed under the License is distributed on an
|
||||
# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
# KIND, either express or implied. See the License for the
|
||||
# specific language governing permissions and limitations
|
||||
# under the License.
|
||||
#
|
||||
-->
|
||||
<plist version="1.0">
|
||||
<dict>
|
||||
<key>CFBundleDevelopmentRegion</key>
|
||||
|
||||
@@ -22,7 +22,7 @@
|
||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:rim="http://www.blackberry.com/ns/widgets"
|
||||
id="cordova-plugin-camera-tests"
|
||||
version="1.0.1-dev">
|
||||
version="1.1.1-dev">
|
||||
<name>Cordova Camera Plugin Tests</name>
|
||||
<license>Apache 2.0</license>
|
||||
|
||||
|
||||