mirror of
https://github.com/apache/cordova-plugin-camera.git
synced 2025-01-18 19:22:51 +08:00
fix!: remove deprecated platforms (#848)
This commit is contained in:
parent
8cb34e1175
commit
20293f3d64
18
README.md
18
README.md
@ -172,8 +172,6 @@ __Supported Platforms__
|
|||||||
- Android
|
- Android
|
||||||
- Browser
|
- Browser
|
||||||
- iOS
|
- iOS
|
||||||
- Windows
|
|
||||||
- OSX
|
|
||||||
|
|
||||||
More examples [here](#camera-getPicture-examples). Quirks [here](#camera-getPicture-quirks).
|
More examples [here](#camera-getPicture-examples). Quirks [here](#camera-getPicture-quirks).
|
||||||
|
|
||||||
@ -476,16 +474,6 @@ displays:
|
|||||||
// do your thing here!
|
// do your thing here!
|
||||||
}, 0);
|
}, 0);
|
||||||
|
|
||||||
#### Windows quirks
|
|
||||||
|
|
||||||
On Windows Phone 8.1 using `SAVEDPHOTOALBUM` or `PHOTOLIBRARY` as a source type causes application to suspend until file picker returns the selected image and
|
|
||||||
then restore with start page as defined in app's `config.xml`. In case when `camera.getPicture` was called from different page, this will lead to reloading
|
|
||||||
start page from scratch and success and error callbacks will never be called.
|
|
||||||
|
|
||||||
To avoid this we suggest using SPA pattern or call `camera.getPicture` only from your app's start page.
|
|
||||||
|
|
||||||
More information about Windows Phone 8.1 picker APIs is here: [How to continue your Windows Phone app after calling a file picker](https://msdn.microsoft.com/en-us/library/windows/apps/dn720490.aspx)
|
|
||||||
|
|
||||||
## `CameraOptions` Errata <a name="CameraOptions-quirks"></a>
|
## `CameraOptions` Errata <a name="CameraOptions-quirks"></a>
|
||||||
|
|
||||||
#### Android Quirks
|
#### Android Quirks
|
||||||
@ -571,12 +559,6 @@ function displayImage(imgUri) {
|
|||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
To display the image on some platforms, you might need to include the main part of the URI in the Content-Security-Policy `<meta>` element in index.html. For example, on Windows 10, you can include `ms-appdata:` in your `<meta>` element. Here is an example.
|
|
||||||
|
|
||||||
```html
|
|
||||||
<meta http-equiv="Content-Security-Policy" content="default-src 'self' data: gap: ms-appdata: https://ssl.gstatic.com 'unsafe-eval'; style-src 'self' 'unsafe-inline'; media-src *">
|
|
||||||
```
|
|
||||||
|
|
||||||
## Take a Picture and Return Thumbnails (Resize the Picture) <a name="getThumbnails"></a>
|
## Take a Picture and Return Thumbnails (Resize the Picture) <a name="getThumbnails"></a>
|
||||||
|
|
||||||
To get smaller images, you can return a resized image by passing both `targetHeight` and `targetWidth` values with your CameraOptions object. In this example, you resize the returned image to fit in a 100px by 100px box (the aspect ratio is maintained, so 100px is either the height or width, whichever is greater in the source).
|
To get smaller images, you can return a resized image by passing both `targetHeight` and `targetWidth` values with your CameraOptions object. In this example, you resize the returned image to fit in a 100px by 100px box (the aspect ratio is maintained, so 100px is either the height or width, whichever is greater in the source).
|
||||||
|
@ -8,9 +8,7 @@
|
|||||||
"platforms": [
|
"platforms": [
|
||||||
"android",
|
"android",
|
||||||
"ios",
|
"ios",
|
||||||
"browser",
|
"browser"
|
||||||
"windows",
|
|
||||||
"osx"
|
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
"repository": "github:apache/cordova-plugin-camera",
|
"repository": "github:apache/cordova-plugin-camera",
|
||||||
@ -21,9 +19,7 @@
|
|||||||
"ecosystem:cordova",
|
"ecosystem:cordova",
|
||||||
"cordova-android",
|
"cordova-android",
|
||||||
"cordova-ios",
|
"cordova-ios",
|
||||||
"cordova-browser",
|
"cordova-browser"
|
||||||
"cordova-windows",
|
|
||||||
"cordova-osx"
|
|
||||||
],
|
],
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"test": "npm run lint",
|
"test": "npm run lint",
|
||||||
|
@ -1,80 +0,0 @@
|
|||||||
/*
|
|
||||||
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.
|
|
||||||
*/
|
|
||||||
#import <Quartz/Quartz.h>
|
|
||||||
#import <AppKit/AppKit.h>
|
|
||||||
#import <Cordova/CDVPlugin.h>
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
enum CDVDestinationType {
|
|
||||||
DestinationTypeDataUrl = 0,
|
|
||||||
DestinationTypeFileUri
|
|
||||||
};
|
|
||||||
typedef NSUInteger CDVDestinationType;
|
|
||||||
|
|
||||||
enum CDVSourceType {
|
|
||||||
SourceTypePhotoLibrary = 0,
|
|
||||||
SourceTypeCamera,
|
|
||||||
SourceTypePhotoAlbum
|
|
||||||
};
|
|
||||||
typedef NSUInteger CDVSourceType;
|
|
||||||
|
|
||||||
enum CDVEncodingType {
|
|
||||||
EncodingTypeJPEG = 0,
|
|
||||||
EncodingTypePNG
|
|
||||||
};
|
|
||||||
typedef NSUInteger CDVEncodingType;
|
|
||||||
|
|
||||||
enum CDVMediaType {
|
|
||||||
MediaTypePicture = 0,
|
|
||||||
MediaTypeVideo,
|
|
||||||
MediaTypeAll
|
|
||||||
};
|
|
||||||
typedef NSUInteger CDVMediaType;
|
|
||||||
|
|
||||||
|
|
||||||
// ======================================================================= //
|
|
||||||
|
|
||||||
|
|
||||||
@interface CDVPictureOptions : NSObject
|
|
||||||
|
|
||||||
@property (strong) NSNumber *quality;
|
|
||||||
@property (assign) CDVDestinationType destinationType;
|
|
||||||
@property (assign) CDVSourceType sourceType;
|
|
||||||
@property (assign) CGSize targetSize;
|
|
||||||
@property (assign) CDVEncodingType encodingType;
|
|
||||||
@property (assign) CDVMediaType mediaType;
|
|
||||||
@property (assign) BOOL allowsEditing;
|
|
||||||
@property (assign) BOOL correctOrientation;
|
|
||||||
@property (assign) BOOL saveToPhotoAlbum;
|
|
||||||
|
|
||||||
+ (instancetype) createFromTakePictureArguments:(CDVInvokedUrlCommand *)command;
|
|
||||||
|
|
||||||
@end
|
|
||||||
|
|
||||||
|
|
||||||
// ======================================================================= //
|
|
||||||
|
|
||||||
|
|
||||||
@interface CDVCamera : CDVPlugin
|
|
||||||
|
|
||||||
- (void)takePicture:(CDVInvokedUrlCommand *)command;
|
|
||||||
- (void)cleanup:(CDVInvokedUrlCommand *)command;
|
|
||||||
|
|
||||||
@end
|
|
@ -1,258 +0,0 @@
|
|||||||
/*
|
|
||||||
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.
|
|
||||||
*/
|
|
||||||
#import "CDVCamera.h"
|
|
||||||
|
|
||||||
|
|
||||||
@implementation CDVPictureOptions
|
|
||||||
|
|
||||||
+ (instancetype) createFromTakePictureArguments:(CDVInvokedUrlCommand*)command {
|
|
||||||
CDVPictureOptions *pictureOptions = [[CDVPictureOptions alloc] init];
|
|
||||||
|
|
||||||
pictureOptions.quality = [command argumentAtIndex:0 withDefault:@(50)];
|
|
||||||
pictureOptions.destinationType = [[command argumentAtIndex:1 withDefault:@(DestinationTypeFileUri)] unsignedIntegerValue];
|
|
||||||
pictureOptions.sourceType = [[command argumentAtIndex:2 withDefault:@(SourceTypeCamera)] unsignedIntegerValue];
|
|
||||||
|
|
||||||
NSNumber *targetWidth = [command argumentAtIndex:3 withDefault:nil];
|
|
||||||
NSNumber *targetHeight = [command argumentAtIndex:4 withDefault:nil];
|
|
||||||
pictureOptions.targetSize = CGSizeMake(0, 0);
|
|
||||||
if ((targetWidth != nil) && (targetHeight != nil)) {
|
|
||||||
pictureOptions.targetSize = CGSizeMake([targetWidth floatValue], [targetHeight floatValue]);
|
|
||||||
}
|
|
||||||
|
|
||||||
pictureOptions.encodingType = [[command argumentAtIndex:5 withDefault:@(EncodingTypeJPEG)] unsignedIntegerValue];
|
|
||||||
pictureOptions.mediaType = [[command argumentAtIndex:6 withDefault:@(MediaTypePicture)] unsignedIntegerValue];
|
|
||||||
pictureOptions.allowsEditing = [[command argumentAtIndex:7 withDefault:@(NO)] boolValue];
|
|
||||||
pictureOptions.correctOrientation = [[command argumentAtIndex:8 withDefault:@(NO)] boolValue];
|
|
||||||
pictureOptions.saveToPhotoAlbum = [[command argumentAtIndex:9 withDefault:@(NO)] boolValue];
|
|
||||||
|
|
||||||
return pictureOptions;
|
|
||||||
}
|
|
||||||
|
|
||||||
@end
|
|
||||||
|
|
||||||
|
|
||||||
// ======================================================================= //
|
|
||||||
|
|
||||||
|
|
||||||
@implementation CDVCamera
|
|
||||||
|
|
||||||
/*!
|
|
||||||
Static array that stores the temporary created files allowing to delete them when calling navigator.camera.cleanup(...)
|
|
||||||
*/
|
|
||||||
static NSMutableArray *cleanUpFiles;
|
|
||||||
|
|
||||||
+ (void)initialize {
|
|
||||||
cleanUpFiles = [NSMutableArray array];
|
|
||||||
}
|
|
||||||
|
|
||||||
- (void)takePicture:(CDVInvokedUrlCommand *)command {
|
|
||||||
CDVPictureOptions *pictureOptions = [CDVPictureOptions createFromTakePictureArguments:command];
|
|
||||||
if (pictureOptions.sourceType == SourceTypeCamera) {
|
|
||||||
[self takePictureFromCamera:command withOptions:pictureOptions];
|
|
||||||
} else {
|
|
||||||
[self takePictureFromFile:command withOptions:pictureOptions];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
- (void)cleanup:(CDVInvokedUrlCommand*)command {
|
|
||||||
[self.commandDelegate runInBackground:^{
|
|
||||||
if (cleanUpFiles.count > 0) {
|
|
||||||
for (int i=0; i<cleanUpFiles.count; i++) {
|
|
||||||
NSString *path = [cleanUpFiles objectAtIndex:i];
|
|
||||||
[[NSFileManager defaultManager] removeItemAtPath:path error:nil];
|
|
||||||
}
|
|
||||||
|
|
||||||
[cleanUpFiles removeAllObjects];
|
|
||||||
|
|
||||||
CDVPluginResult *result = [CDVPluginResult resultWithStatus:CDVCommandStatus_OK];
|
|
||||||
[self.commandDelegate sendPluginResult:result callbackId:command.callbackId];
|
|
||||||
}
|
|
||||||
}];
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
#pragma mark - Camera
|
|
||||||
|
|
||||||
/*!
|
|
||||||
Takes a picture from the iSight camera using the default OS dialog.
|
|
||||||
@see https://developer.apple.com/documentation/quartz/ikpicturetaker
|
|
||||||
*/
|
|
||||||
- (void)takePictureFromCamera:(CDVInvokedUrlCommand *)command withOptions:(CDVPictureOptions *)pictureOptions {
|
|
||||||
IKPictureTaker *pictureTaker = [IKPictureTaker pictureTaker];
|
|
||||||
[pictureTaker setValue:[NSNumber numberWithBool:YES] forKey:IKPictureTakerAllowsVideoCaptureKey];
|
|
||||||
[pictureTaker setValue:[NSNumber numberWithBool:NO] forKey:IKPictureTakerAllowsFileChoosingKey];
|
|
||||||
[pictureTaker setValue:[NSNumber numberWithBool:pictureOptions.allowsEditing] forKey:IKPictureTakerShowEffectsKey];
|
|
||||||
[pictureTaker setValue:[NSNumber numberWithBool:pictureOptions.allowsEditing] forKey:IKPictureTakerAllowsEditingKey];
|
|
||||||
|
|
||||||
NSDictionary *contextInfo = @{ @"command": command, @"pictureOptions" : pictureOptions};
|
|
||||||
[pictureTaker beginPictureTakerSheetForWindow:self.viewController.contentView.window withDelegate:self didEndSelector:@selector(pictureTakerDidEnd:returnCode:contextInfo:) contextInfo:(void *)CFBridgingRetain(contextInfo)];
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
- (void)pictureTakerDidEnd:(IKPictureTaker *)pictureTaker returnCode:(NSInteger)returnCode contextInfo:(void *)contextInfo {
|
|
||||||
if (returnCode == NSOKButton) {
|
|
||||||
NSDictionary *contextInfoDictionary = (NSDictionary *)CFBridgingRelease(contextInfo);
|
|
||||||
CDVInvokedUrlCommand *command = [contextInfoDictionary valueForKey:@"command"];
|
|
||||||
CDVPictureOptions *pictureOptions = [contextInfoDictionary valueForKey:@"pictureOptions"];
|
|
||||||
|
|
||||||
[self returnImage:pictureTaker.outputImage command:command options:pictureOptions];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
#pragma mark - File
|
|
||||||
|
|
||||||
/*!
|
|
||||||
Allows to select an image or video using the system native dialog.
|
|
||||||
*/
|
|
||||||
- (void)takePictureFromFile:(CDVInvokedUrlCommand *)command withOptions:(CDVPictureOptions *)pictureOptions {
|
|
||||||
NSOpenPanel *openPanel = [NSOpenPanel openPanel];
|
|
||||||
openPanel.canChooseFiles = YES;
|
|
||||||
openPanel.canChooseDirectories = NO;
|
|
||||||
openPanel.canCreateDirectories = YES;
|
|
||||||
openPanel.allowsMultipleSelection = NO;
|
|
||||||
|
|
||||||
NSMutableArray *allowedTypes = [NSMutableArray array];
|
|
||||||
if (pictureOptions.mediaType == MediaTypePicture || pictureOptions.mediaType == MediaTypeAll) {
|
|
||||||
[allowedTypes addObjectsFromArray:[NSImage imageTypes]];
|
|
||||||
}
|
|
||||||
if (pictureOptions.mediaType == MediaTypeVideo || pictureOptions.mediaType == MediaTypeAll) {
|
|
||||||
[allowedTypes addObjectsFromArray:@[(NSString *)kUTTypeMovie]];
|
|
||||||
}
|
|
||||||
[openPanel setAllowedFileTypes:allowedTypes];
|
|
||||||
|
|
||||||
[openPanel beginSheetModalForWindow:self.viewController.contentView.window completionHandler:^(NSInteger result) {
|
|
||||||
if (result == NSOKButton) {
|
|
||||||
NSURL *fileURL = [openPanel.URLs objectAtIndex:0];
|
|
||||||
|
|
||||||
if ([self fileIsImage:fileURL]) {
|
|
||||||
NSImage *image = [[NSImage alloc] initWithContentsOfFile:fileURL.path];
|
|
||||||
[self returnImage:image command:command options:pictureOptions];
|
|
||||||
} else {
|
|
||||||
if (pictureOptions.destinationType == DestinationTypeDataUrl) {
|
|
||||||
CDVPluginResult *result = [CDVPluginResult resultWithStatus:CDVCommandStatus_ERROR messageAsString:@"Camera.DestinationType.DATA_URL is only available with image files"];
|
|
||||||
[self.commandDelegate sendPluginResult:result callbackId:command.callbackId];
|
|
||||||
} else {
|
|
||||||
[self returnUri:fileURL.path command:command options:pictureOptions];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}];
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
#pragma mark - Common
|
|
||||||
|
|
||||||
/*!
|
|
||||||
Returns to JavaScript a URI.
|
|
||||||
Called when Camera.DestinationType.FILE_URI.
|
|
||||||
*/
|
|
||||||
- (void)returnUri:(NSString *)path command:(CDVInvokedUrlCommand *)command options:(CDVPictureOptions *)pictureOptions {
|
|
||||||
NSString *protocol = (pictureOptions.destinationType == DestinationTypeFileUri) ? @"file://" : @"";
|
|
||||||
NSString *uri = [NSString stringWithFormat:@"%@%@", protocol, path];
|
|
||||||
|
|
||||||
CDVPluginResult *result = [CDVPluginResult resultWithStatus:CDVCommandStatus_OK messageAsString:uri];
|
|
||||||
[self.commandDelegate sendPluginResult:result callbackId:command.callbackId];
|
|
||||||
}
|
|
||||||
|
|
||||||
/*!
|
|
||||||
Returns to JavaScript a base64 encoded image.
|
|
||||||
Called when Camera.DestinationType.DATA_URL.
|
|
||||||
*/
|
|
||||||
- (void)returnImage:(NSImage *)image command:(CDVInvokedUrlCommand *)command options:(CDVPictureOptions *)pictureOptions {
|
|
||||||
[self.commandDelegate runInBackground:^{
|
|
||||||
NSData *processedImageData = [self processImage:image options:pictureOptions];
|
|
||||||
|
|
||||||
if (pictureOptions.destinationType == DestinationTypeDataUrl) {
|
|
||||||
CDVPluginResult *result = [CDVPluginResult resultWithStatus:CDVCommandStatus_OK messageAsString:[processedImageData base64EncodedStringWithOptions:NSDataBase64EncodingEndLineWithLineFeed]];
|
|
||||||
[self.commandDelegate sendPluginResult:result callbackId:command.callbackId];
|
|
||||||
} else {
|
|
||||||
NSString *tempFilePath = [self uniqueImageName:pictureOptions];
|
|
||||||
[processedImageData writeToFile:tempFilePath atomically:YES];
|
|
||||||
[cleanUpFiles addObject:tempFilePath];
|
|
||||||
[self returnUri:tempFilePath command:command options:pictureOptions];
|
|
||||||
}
|
|
||||||
}];
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
/*!
|
|
||||||
Top level method to apply the size and quality required changes to an image.
|
|
||||||
*/
|
|
||||||
- (NSData *)processImage:(NSImage *)image options:(CDVPictureOptions *)pictureOptions {
|
|
||||||
NSImage *sourceImage = image;
|
|
||||||
if (pictureOptions.targetSize.width > 0 && pictureOptions.targetSize.height > 0) {
|
|
||||||
sourceImage = [self resizeImage:sourceImage toSize:pictureOptions.targetSize];
|
|
||||||
}
|
|
||||||
|
|
||||||
CGImageRef cgRef = [sourceImage CGImageForProposedRect:NULL context:nil hints:nil];
|
|
||||||
NSBitmapImageRep *imageRepresentation = [[NSBitmapImageRep alloc] initWithCGImage:cgRef];
|
|
||||||
|
|
||||||
NSData *data = (pictureOptions.encodingType == EncodingTypeJPEG)
|
|
||||||
? [imageRepresentation representationUsingType:NSJPEGFileType properties:@{NSImageCompressionFactor: [NSNumber numberWithFloat:pictureOptions.quality.floatValue/100.f]}]
|
|
||||||
: [imageRepresentation representationUsingType:NSPNGFileType properties:@{NSImageCompressionFactor: @1.0}];
|
|
||||||
|
|
||||||
return data;
|
|
||||||
}
|
|
||||||
|
|
||||||
/*!
|
|
||||||
Auxiliar method to resize an image.
|
|
||||||
*/
|
|
||||||
- (NSImage *)resizeImage:(NSImage *)image toSize:(CGSize)newSize {
|
|
||||||
CGFloat aspectWidth = newSize.width / image.size.width;
|
|
||||||
CGFloat aspectHeight = newSize.height / image.size.height;
|
|
||||||
CGFloat aspectRatio = MIN(aspectWidth, aspectHeight);
|
|
||||||
|
|
||||||
CGSize scaledSize = NSMakeSize(image.size.width*aspectRatio, image.size.height*aspectRatio);
|
|
||||||
|
|
||||||
NSImage *smallImage = [[NSImage alloc] initWithSize: scaledSize];
|
|
||||||
[smallImage lockFocus];
|
|
||||||
[image setSize: scaledSize];
|
|
||||||
[[NSGraphicsContext currentContext] setImageInterpolation:NSImageInterpolationHigh];
|
|
||||||
[image drawAtPoint:NSZeroPoint fromRect:CGRectMake(0, 0, scaledSize.width, scaledSize.height) operation:NSCompositeCopy fraction:1.0];
|
|
||||||
[smallImage unlockFocus];
|
|
||||||
return smallImage;
|
|
||||||
}
|
|
||||||
|
|
||||||
/*!
|
|
||||||
Auxiliar method to know if a given file is an image or not.
|
|
||||||
*/
|
|
||||||
- (BOOL)fileIsImage:(NSURL *)fileURL {
|
|
||||||
NSString *type;
|
|
||||||
BOOL isImage = NO;
|
|
||||||
|
|
||||||
if ([fileURL getResourceValue:&type forKey:NSURLTypeIdentifierKey error:nil]) {
|
|
||||||
isImage = [[NSImage imageTypes] containsObject:type];
|
|
||||||
}
|
|
||||||
|
|
||||||
return isImage;
|
|
||||||
}
|
|
||||||
|
|
||||||
/*!
|
|
||||||
Auxiliar method that generates an unique filename for an image in the temporary directory.
|
|
||||||
*/
|
|
||||||
- (NSString *)uniqueImageName:(CDVPictureOptions *)pictureOptions {
|
|
||||||
NSString *tempDir = NSTemporaryDirectory();
|
|
||||||
NSString *guid = [[NSProcessInfo processInfo] globallyUniqueString] ;
|
|
||||||
NSString *extension = (pictureOptions.encodingType == EncodingTypeJPEG) ? @"jpeg" : @"png";
|
|
||||||
NSString *uniqueFileName = [NSString stringWithFormat:@"%@%@.%@", tempDir, guid, extension];
|
|
||||||
return uniqueFileName;
|
|
||||||
}
|
|
||||||
|
|
||||||
@end
|
|
@ -1,861 +0,0 @@
|
|||||||
/*
|
|
||||||
*
|
|
||||||
* 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.
|
|
||||||
*
|
|
||||||
*/
|
|
||||||
|
|
||||||
/* global Windows:true, URL:true, module:true, require:true, WinJS:true */
|
|
||||||
|
|
||||||
const Camera = require('./Camera');
|
|
||||||
|
|
||||||
const getAppData = function () {
|
|
||||||
return Windows.Storage.ApplicationData.current;
|
|
||||||
};
|
|
||||||
const encodeToBase64String = function (buffer) {
|
|
||||||
return Windows.Security.Cryptography.CryptographicBuffer.encodeToBase64String(buffer);
|
|
||||||
};
|
|
||||||
const OptUnique = Windows.Storage.CreationCollisionOption.generateUniqueName;
|
|
||||||
const CapMSType = Windows.Media.Capture.MediaStreamType;
|
|
||||||
const webUIApp = Windows.UI.WebUI.WebUIApplication;
|
|
||||||
const fileIO = Windows.Storage.FileIO;
|
|
||||||
const pickerLocId = Windows.Storage.Pickers.PickerLocationId;
|
|
||||||
|
|
||||||
module.exports = {
|
|
||||||
|
|
||||||
// args will contain :
|
|
||||||
// ... it is an array, so be careful
|
|
||||||
// 0 quality:50,
|
|
||||||
// 1 destinationType:Camera.DestinationType.FILE_URI,
|
|
||||||
// 2 sourceType:Camera.PictureSourceType.CAMERA,
|
|
||||||
// 3 targetWidth:-1,
|
|
||||||
// 4 targetHeight:-1,
|
|
||||||
// 5 encodingType:Camera.EncodingType.JPEG,
|
|
||||||
// 6 mediaType:Camera.MediaType.PICTURE,
|
|
||||||
// 7 allowEdit:false,
|
|
||||||
// 8 correctOrientation:false,
|
|
||||||
// 9 saveToPhotoAlbum:false,
|
|
||||||
// 10 popoverOptions:null
|
|
||||||
// 11 cameraDirection:0
|
|
||||||
|
|
||||||
takePicture: function (successCallback, errorCallback, args) {
|
|
||||||
const sourceType = args[2];
|
|
||||||
|
|
||||||
if (sourceType !== Camera.PictureSourceType.CAMERA) {
|
|
||||||
takePictureFromFile(successCallback, errorCallback, args);
|
|
||||||
} else {
|
|
||||||
takePictureFromCamera(successCallback, errorCallback, args);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
// https://msdn.microsoft.com/en-us/library/windows/apps/ff462087(v=vs.105).aspx
|
|
||||||
const windowsVideoContainers = ['.avi', '.flv', '.asx', '.asf', '.mov', '.mp4', '.mpg', '.rm', '.srt', '.swf', '.wmv', '.vob'];
|
|
||||||
const windowsPhoneVideoContainers = ['.avi', '.3gp', '.3g2', '.wmv', '.3gp', '.3g2', '.mp4', '.m4v'];
|
|
||||||
|
|
||||||
// Default aspect ratio 1.78 (16:9 hd video standard)
|
|
||||||
const DEFAULT_ASPECT_RATIO = '1.8';
|
|
||||||
|
|
||||||
// Highest possible z-index supported across browsers. Anything used above is converted to this value.
|
|
||||||
const HIGHEST_POSSIBLE_Z_INDEX = 2147483647;
|
|
||||||
|
|
||||||
// Resize method
|
|
||||||
function resizeImage (successCallback, errorCallback, file, targetWidth, targetHeight, encodingType) {
|
|
||||||
let tempPhotoFileName = '';
|
|
||||||
let targetContentType = '';
|
|
||||||
|
|
||||||
if (encodingType === Camera.EncodingType.PNG) {
|
|
||||||
tempPhotoFileName = 'camera_cordova_temp_return.png';
|
|
||||||
targetContentType = 'image/png';
|
|
||||||
} else {
|
|
||||||
tempPhotoFileName = 'camera_cordova_temp_return.jpg';
|
|
||||||
targetContentType = 'image/jpeg';
|
|
||||||
}
|
|
||||||
|
|
||||||
const storageFolder = getAppData().localFolder;
|
|
||||||
file.copyAsync(storageFolder, file.name, Windows.Storage.NameCollisionOption.replaceExisting)
|
|
||||||
.then(function (storageFile) {
|
|
||||||
return fileIO.readBufferAsync(storageFile);
|
|
||||||
})
|
|
||||||
.then(function (buffer) {
|
|
||||||
const strBase64 = encodeToBase64String(buffer);
|
|
||||||
const imageData = 'data:' + file.contentType + ';base64,' + strBase64;
|
|
||||||
const image = new Image(); /* eslint no-undef : 0 */
|
|
||||||
image.src = imageData;
|
|
||||||
image.onload = function () {
|
|
||||||
const ratio = Math.min(targetWidth / this.width, targetHeight / this.height);
|
|
||||||
const imageWidth = ratio * this.width;
|
|
||||||
const imageHeight = ratio * this.height;
|
|
||||||
|
|
||||||
const canvas = document.createElement('canvas');
|
|
||||||
let storageFileName;
|
|
||||||
|
|
||||||
canvas.width = imageWidth;
|
|
||||||
canvas.height = imageHeight;
|
|
||||||
|
|
||||||
canvas.getContext('2d').drawImage(this, 0, 0, imageWidth, imageHeight);
|
|
||||||
|
|
||||||
const fileContent = canvas.toDataURL(targetContentType).split(',')[1];
|
|
||||||
|
|
||||||
const storageFolder = getAppData().localFolder;
|
|
||||||
|
|
||||||
storageFolder.createFileAsync(tempPhotoFileName, OptUnique)
|
|
||||||
.then(function (storagefile) {
|
|
||||||
const content = Windows.Security.Cryptography.CryptographicBuffer.decodeFromBase64String(fileContent);
|
|
||||||
storageFileName = storagefile.name;
|
|
||||||
return fileIO.writeBufferAsync(storagefile, content);
|
|
||||||
})
|
|
||||||
.done(function () {
|
|
||||||
successCallback('ms-appdata:///local/' + storageFileName);
|
|
||||||
}, errorCallback);
|
|
||||||
};
|
|
||||||
})
|
|
||||||
.done(null, function (err) {
|
|
||||||
errorCallback(err);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
// Because of asynchronous method, so let the successCallback be called in it.
|
|
||||||
function resizeImageBase64 (successCallback, errorCallback, file, targetWidth, targetHeight) {
|
|
||||||
fileIO.readBufferAsync(file).done(function (buffer) {
|
|
||||||
const strBase64 = encodeToBase64String(buffer);
|
|
||||||
const imageData = 'data:' + file.contentType + ';base64,' + strBase64;
|
|
||||||
|
|
||||||
const image = new Image(); /* eslint no-undef : 0 */
|
|
||||||
image.src = imageData;
|
|
||||||
|
|
||||||
image.onload = function () {
|
|
||||||
const ratio = Math.min(targetWidth / this.width, targetHeight / this.height);
|
|
||||||
const imageWidth = ratio * this.width;
|
|
||||||
const imageHeight = ratio * this.height;
|
|
||||||
const canvas = document.createElement('canvas');
|
|
||||||
|
|
||||||
canvas.width = imageWidth;
|
|
||||||
canvas.height = imageHeight;
|
|
||||||
|
|
||||||
const ctx = canvas.getContext('2d');
|
|
||||||
ctx.drawImage(this, 0, 0, imageWidth, imageHeight);
|
|
||||||
|
|
||||||
// The resized file ready for upload
|
|
||||||
const finalFile = canvas.toDataURL(file.contentType);
|
|
||||||
|
|
||||||
// Remove the prefix such as "data:" + contentType + ";base64," , in order to meet the Cordova API.
|
|
||||||
const arr = finalFile.split(',');
|
|
||||||
const newStr = finalFile.substr(arr[0].length + 1);
|
|
||||||
successCallback(newStr);
|
|
||||||
};
|
|
||||||
}, function (err) { errorCallback(err); });
|
|
||||||
}
|
|
||||||
|
|
||||||
function takePictureFromFile (successCallback, errorCallback, args) {
|
|
||||||
// Detect Windows Phone
|
|
||||||
if (navigator.appVersion.indexOf('Windows Phone 8.1') >= 0) {
|
|
||||||
takePictureFromFileWP(successCallback, errorCallback, args);
|
|
||||||
} else {
|
|
||||||
takePictureFromFileWindows(successCallback, errorCallback, args);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function takePictureFromFileWP (successCallback, errorCallback, args) {
|
|
||||||
const mediaType = args[6];
|
|
||||||
const destinationType = args[1];
|
|
||||||
const targetWidth = args[3];
|
|
||||||
const targetHeight = args[4];
|
|
||||||
const encodingType = args[5];
|
|
||||||
|
|
||||||
/*
|
|
||||||
Need to add and remove an event listener to catch activation state
|
|
||||||
Using FileOpenPicker will suspend the app and it's required to catch the PickSingleFileAndContinue
|
|
||||||
https://msdn.microsoft.com/en-us/library/windows/apps/xaml/dn631755.aspx
|
|
||||||
*/
|
|
||||||
const filePickerActivationHandler = function (eventArgs) {
|
|
||||||
if (eventArgs.kind === Windows.ApplicationModel.Activation.ActivationKind.pickFileContinuation) {
|
|
||||||
const file = eventArgs.files[0];
|
|
||||||
if (!file) {
|
|
||||||
errorCallback("User didn't choose a file.");
|
|
||||||
webUIApp.removeEventListener('activated', filePickerActivationHandler);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (destinationType === Camera.DestinationType.FILE_URI) {
|
|
||||||
if (targetHeight > 0 && targetWidth > 0) {
|
|
||||||
resizeImage(successCallback, errorCallback, file, targetWidth, targetHeight, encodingType);
|
|
||||||
} else {
|
|
||||||
const storageFolder = getAppData().localFolder;
|
|
||||||
file.copyAsync(storageFolder, file.name, Windows.Storage.NameCollisionOption.replaceExisting).done(function (storageFile) {
|
|
||||||
successCallback(URL.createObjectURL(storageFile));
|
|
||||||
}, function () {
|
|
||||||
errorCallback("Can't access localStorage folder.");
|
|
||||||
});
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
if (targetHeight > 0 && targetWidth > 0) {
|
|
||||||
resizeImageBase64(successCallback, errorCallback, file, targetWidth, targetHeight);
|
|
||||||
} else {
|
|
||||||
fileIO.readBufferAsync(file).done(function (buffer) {
|
|
||||||
const strBase64 = encodeToBase64String(buffer);
|
|
||||||
successCallback(strBase64);
|
|
||||||
}, errorCallback);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
webUIApp.removeEventListener('activated', filePickerActivationHandler);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
const fileOpenPicker = new Windows.Storage.Pickers.FileOpenPicker();
|
|
||||||
if (mediaType === Camera.MediaType.PICTURE) {
|
|
||||||
fileOpenPicker.fileTypeFilter.replaceAll(['.png', '.jpg', '.jpeg']);
|
|
||||||
fileOpenPicker.suggestedStartLocation = pickerLocId.picturesLibrary;
|
|
||||||
} else if (mediaType === Camera.MediaType.VIDEO) {
|
|
||||||
fileOpenPicker.fileTypeFilter.replaceAll(windowsPhoneVideoContainers);
|
|
||||||
fileOpenPicker.suggestedStartLocation = pickerLocId.videosLibrary;
|
|
||||||
} else {
|
|
||||||
fileOpenPicker.fileTypeFilter.replaceAll(['*']);
|
|
||||||
fileOpenPicker.suggestedStartLocation = pickerLocId.documentsLibrary;
|
|
||||||
}
|
|
||||||
|
|
||||||
webUIApp.addEventListener('activated', filePickerActivationHandler);
|
|
||||||
fileOpenPicker.pickSingleFileAndContinue();
|
|
||||||
}
|
|
||||||
|
|
||||||
function takePictureFromFileWindows (successCallback, errorCallback, args) {
|
|
||||||
const mediaType = args[6];
|
|
||||||
const destinationType = args[1];
|
|
||||||
const targetWidth = args[3];
|
|
||||||
const targetHeight = args[4];
|
|
||||||
const encodingType = args[5];
|
|
||||||
|
|
||||||
const fileOpenPicker = new Windows.Storage.Pickers.FileOpenPicker();
|
|
||||||
if (mediaType === Camera.MediaType.PICTURE) {
|
|
||||||
fileOpenPicker.fileTypeFilter.replaceAll(['.png', '.jpg', '.jpeg']);
|
|
||||||
fileOpenPicker.suggestedStartLocation = pickerLocId.picturesLibrary;
|
|
||||||
} else if (mediaType === Camera.MediaType.VIDEO) {
|
|
||||||
fileOpenPicker.fileTypeFilter.replaceAll(windowsVideoContainers);
|
|
||||||
fileOpenPicker.suggestedStartLocation = pickerLocId.videosLibrary;
|
|
||||||
} else {
|
|
||||||
fileOpenPicker.fileTypeFilter.replaceAll(['*']);
|
|
||||||
fileOpenPicker.suggestedStartLocation = pickerLocId.documentsLibrary;
|
|
||||||
}
|
|
||||||
|
|
||||||
fileOpenPicker.pickSingleFileAsync().done(function (file) {
|
|
||||||
if (!file) {
|
|
||||||
errorCallback("User didn't choose a file.");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (destinationType === Camera.DestinationType.FILE_URI) {
|
|
||||||
if (targetHeight > 0 && targetWidth > 0) {
|
|
||||||
resizeImage(successCallback, errorCallback, file, targetWidth, targetHeight, encodingType);
|
|
||||||
} else {
|
|
||||||
const storageFolder = getAppData().localFolder;
|
|
||||||
file.copyAsync(storageFolder, file.name, Windows.Storage.NameCollisionOption.replaceExisting).done(function (storageFile) {
|
|
||||||
successCallback(URL.createObjectURL(storageFile));
|
|
||||||
}, function () {
|
|
||||||
errorCallback("Can't access localStorage folder.");
|
|
||||||
});
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
if (targetHeight > 0 && targetWidth > 0) {
|
|
||||||
resizeImageBase64(successCallback, errorCallback, file, targetWidth, targetHeight);
|
|
||||||
} else {
|
|
||||||
fileIO.readBufferAsync(file).done(function (buffer) {
|
|
||||||
const strBase64 = encodeToBase64String(buffer);
|
|
||||||
successCallback(strBase64);
|
|
||||||
}, errorCallback);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}, function () {
|
|
||||||
errorCallback("User didn't choose a file.");
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
function takePictureFromCamera (successCallback, errorCallback, args) {
|
|
||||||
// Check if necessary API available
|
|
||||||
if (!Windows.Media.Capture.CameraCaptureUI) {
|
|
||||||
takePictureFromCameraWP(successCallback, errorCallback, args);
|
|
||||||
} else {
|
|
||||||
takePictureFromCameraWindows(successCallback, errorCallback, args);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function takePictureFromCameraWP (successCallback, errorCallback, args) {
|
|
||||||
// We are running on WP8.1 which lacks CameraCaptureUI class
|
|
||||||
// so we need to use MediaCapture class instead and implement custom UI for camera
|
|
||||||
const destinationType = args[1];
|
|
||||||
const targetWidth = args[3];
|
|
||||||
const targetHeight = args[4];
|
|
||||||
const encodingType = args[5];
|
|
||||||
const saveToPhotoAlbum = args[9];
|
|
||||||
const cameraDirection = args[11];
|
|
||||||
let capturePreview = null;
|
|
||||||
let cameraCaptureButton = null;
|
|
||||||
let cameraCancelButton = null;
|
|
||||||
let capture = null;
|
|
||||||
let captureSettings = null;
|
|
||||||
const CaptureNS = Windows.Media.Capture;
|
|
||||||
let sensor = null;
|
|
||||||
|
|
||||||
function createCameraUI () {
|
|
||||||
// create style for take and cancel buttons
|
|
||||||
const buttonStyle = 'width:45%;padding: 10px 16px;font-size: 18px;line-height: 1.3333333;color: #333;background-color: #fff;border-color: #ccc; border: 1px solid transparent;border-radius: 6px; display: block; margin: 20px; z-index: 1000;border-color: #adadad;';
|
|
||||||
|
|
||||||
// Create fullscreen preview
|
|
||||||
// z-order style element for capturePreview and cameraCancelButton elts
|
|
||||||
// is necessary to avoid overriding by another page elements, -1 sometimes is not enough
|
|
||||||
capturePreview = document.createElement('video');
|
|
||||||
capturePreview.style.cssText = 'position: fixed; left: 0; top: 0; width: 100%; height: 100%; z-index: ' + (HIGHEST_POSSIBLE_Z_INDEX - 1) + ';';
|
|
||||||
|
|
||||||
// Create capture button
|
|
||||||
cameraCaptureButton = document.createElement('button');
|
|
||||||
cameraCaptureButton.innerText = 'Take';
|
|
||||||
cameraCaptureButton.style.cssText = buttonStyle + 'position: fixed; left: 0; bottom: 0; margin: 20px; z-index: ' + HIGHEST_POSSIBLE_Z_INDEX + ';';
|
|
||||||
|
|
||||||
// Create cancel button
|
|
||||||
cameraCancelButton = document.createElement('button');
|
|
||||||
cameraCancelButton.innerText = 'Cancel';
|
|
||||||
cameraCancelButton.style.cssText = buttonStyle + 'position: fixed; right: 0; bottom: 0; margin: 20px; z-index: ' + HIGHEST_POSSIBLE_Z_INDEX + ';';
|
|
||||||
|
|
||||||
capture = new CaptureNS.MediaCapture();
|
|
||||||
|
|
||||||
captureSettings = new CaptureNS.MediaCaptureInitializationSettings();
|
|
||||||
captureSettings.streamingCaptureMode = CaptureNS.StreamingCaptureMode.video;
|
|
||||||
}
|
|
||||||
|
|
||||||
function continueVideoOnFocus () {
|
|
||||||
// if preview is defined it would be stuck, play it
|
|
||||||
if (capturePreview) {
|
|
||||||
capturePreview.play();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function startCameraPreview () {
|
|
||||||
// Search for available camera devices
|
|
||||||
// This is necessary to detect which camera (front or back) we should use
|
|
||||||
const DeviceEnum = Windows.Devices.Enumeration;
|
|
||||||
const expectedPanel = cameraDirection === 1 ? DeviceEnum.Panel.front : DeviceEnum.Panel.back;
|
|
||||||
|
|
||||||
// Add focus event handler to capture the event when user suspends the app and comes back while the preview is on
|
|
||||||
window.addEventListener('focus', continueVideoOnFocus);
|
|
||||||
|
|
||||||
DeviceEnum.DeviceInformation.findAllAsync(DeviceEnum.DeviceClass.videoCapture).then(function (devices) {
|
|
||||||
if (devices.length <= 0) {
|
|
||||||
destroyCameraPreview();
|
|
||||||
errorCallback('Camera not found');
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
devices.forEach(function (currDev) {
|
|
||||||
if (currDev.enclosureLocation.panel && currDev.enclosureLocation.panel === expectedPanel) {
|
|
||||||
captureSettings.videoDeviceId = currDev.id;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
captureSettings.photoCaptureSource = Windows.Media.Capture.PhotoCaptureSource.photo;
|
|
||||||
|
|
||||||
return capture.initializeAsync(captureSettings);
|
|
||||||
}).then(function () {
|
|
||||||
// create focus control if available
|
|
||||||
const VideoDeviceController = capture.videoDeviceController;
|
|
||||||
const FocusControl = VideoDeviceController.focusControl;
|
|
||||||
|
|
||||||
if (FocusControl.supported === true) {
|
|
||||||
capturePreview.addEventListener('click', function () {
|
|
||||||
// Make sure function isn't called again before previous focus is completed
|
|
||||||
if (this.getAttribute('clicked') === '1') {
|
|
||||||
return false;
|
|
||||||
} else {
|
|
||||||
this.setAttribute('clicked', '1');
|
|
||||||
}
|
|
||||||
const preset = Windows.Media.Devices.FocusPreset.autoNormal;
|
|
||||||
const parent = this;
|
|
||||||
FocusControl.setPresetAsync(preset).done(function () {
|
|
||||||
// set the clicked attribute back to '0' to allow focus again
|
|
||||||
parent.setAttribute('clicked', '0');
|
|
||||||
});
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
// msdn.microsoft.com/en-us/library/windows/apps/hh452807.aspx
|
|
||||||
capturePreview.msZoom = true;
|
|
||||||
capturePreview.src = URL.createObjectURL(capture);
|
|
||||||
capturePreview.play();
|
|
||||||
|
|
||||||
// Bind events to controls
|
|
||||||
sensor = Windows.Devices.Sensors.SimpleOrientationSensor.getDefault();
|
|
||||||
if (sensor !== null) {
|
|
||||||
sensor.addEventListener('orientationchanged', onOrientationChange);
|
|
||||||
}
|
|
||||||
|
|
||||||
// add click events to capture and cancel buttons
|
|
||||||
cameraCaptureButton.addEventListener('click', onCameraCaptureButtonClick);
|
|
||||||
cameraCancelButton.addEventListener('click', onCameraCancelButtonClick);
|
|
||||||
|
|
||||||
// Change default orientation
|
|
||||||
if (sensor) {
|
|
||||||
setPreviewRotation(sensor.getCurrentOrientation());
|
|
||||||
} else {
|
|
||||||
setPreviewRotation(Windows.Graphics.Display.DisplayInformation.getForCurrentView().currentOrientation);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Get available aspect ratios
|
|
||||||
const aspectRatios = getAspectRatios(capture);
|
|
||||||
|
|
||||||
// Couldn't find a good ratio
|
|
||||||
if (aspectRatios.length === 0) {
|
|
||||||
destroyCameraPreview();
|
|
||||||
errorCallback('There\'s not a good aspect ratio available');
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// add elements to body
|
|
||||||
document.body.appendChild(capturePreview);
|
|
||||||
document.body.appendChild(cameraCaptureButton);
|
|
||||||
document.body.appendChild(cameraCancelButton);
|
|
||||||
|
|
||||||
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);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
function destroyCameraPreview () {
|
|
||||||
// If sensor is available, remove event listener
|
|
||||||
if (sensor !== null) {
|
|
||||||
sensor.removeEventListener('orientationchanged', onOrientationChange);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Pause and dispose preview element
|
|
||||||
capturePreview.pause();
|
|
||||||
capturePreview.src = null;
|
|
||||||
|
|
||||||
// Remove event listeners from buttons
|
|
||||||
cameraCaptureButton.removeEventListener('click', onCameraCaptureButtonClick);
|
|
||||||
cameraCancelButton.removeEventListener('click', onCameraCancelButtonClick);
|
|
||||||
|
|
||||||
// Remove the focus event handler
|
|
||||||
window.removeEventListener('focus', continueVideoOnFocus);
|
|
||||||
|
|
||||||
// Remove elements
|
|
||||||
[capturePreview, cameraCaptureButton, cameraCancelButton].forEach(function (elem) {
|
|
||||||
if (elem /* && elem in document.body.childNodes */) {
|
|
||||||
document.body.removeChild(elem);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
// Stop and dispose media capture manager
|
|
||||||
if (capture) {
|
|
||||||
capture.stopRecordAsync();
|
|
||||||
capture = null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function captureAction () {
|
|
||||||
let encodingProperties;
|
|
||||||
let fileName;
|
|
||||||
const tempFolder = getAppData().temporaryFolder;
|
|
||||||
|
|
||||||
if (encodingType === Camera.EncodingType.PNG) {
|
|
||||||
fileName = 'photo.png';
|
|
||||||
encodingProperties = Windows.Media.MediaProperties.ImageEncodingProperties.createPng();
|
|
||||||
} else {
|
|
||||||
fileName = 'photo.jpg';
|
|
||||||
encodingProperties = Windows.Media.MediaProperties.ImageEncodingProperties.createJpeg();
|
|
||||||
}
|
|
||||||
|
|
||||||
tempFolder.createFileAsync(fileName, OptUnique)
|
|
||||||
.then(function (tempCapturedFile) {
|
|
||||||
return new WinJS.Promise(function (complete) {
|
|
||||||
const photoStream = new Windows.Storage.Streams.InMemoryRandomAccessStream();
|
|
||||||
const finalStream = new Windows.Storage.Streams.InMemoryRandomAccessStream();
|
|
||||||
capture.capturePhotoToStreamAsync(encodingProperties, photoStream)
|
|
||||||
.then(function () {
|
|
||||||
return Windows.Graphics.Imaging.BitmapDecoder.createAsync(photoStream);
|
|
||||||
})
|
|
||||||
.then(function (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 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.copyAndCloseAsync(finalStream, fileStream);
|
|
||||||
})
|
|
||||||
.done(function () {
|
|
||||||
photoStream.close();
|
|
||||||
finalStream.close();
|
|
||||||
complete(tempCapturedFile);
|
|
||||||
}, function () {
|
|
||||||
photoStream.close();
|
|
||||||
finalStream.close();
|
|
||||||
throw new Error('An error has occured while capturing the photo.');
|
|
||||||
});
|
|
||||||
});
|
|
||||||
})
|
|
||||||
.done(function (capturedFile) {
|
|
||||||
destroyCameraPreview();
|
|
||||||
savePhoto(capturedFile, {
|
|
||||||
destinationType,
|
|
||||||
targetHeight,
|
|
||||||
targetWidth,
|
|
||||||
encodingType,
|
|
||||||
saveToPhotoAlbum
|
|
||||||
}, successCallback, errorCallback);
|
|
||||||
}, function (err) {
|
|
||||||
destroyCameraPreview();
|
|
||||||
errorCallback(err);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
function getAspectRatios (capture) {
|
|
||||||
const videoDeviceController = capture.videoDeviceController;
|
|
||||||
const 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)); });
|
|
||||||
|
|
||||||
const 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)); });
|
|
||||||
|
|
||||||
const 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)); });
|
|
||||||
|
|
||||||
const allAspectRatios = [].concat(photoAspectRatios, videoAspectRatios, videoPreviewAspectRatios);
|
|
||||||
|
|
||||||
const 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;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
function setAspectRatio (capture, aspect) {
|
|
||||||
// Max photo resolution with desired aspect ratio
|
|
||||||
const videoDeviceController = capture.videoDeviceController;
|
|
||||||
const 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
|
|
||||||
const 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
|
|
||||||
const 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 Capture button is clicked, try to capture a picture and return
|
|
||||||
*/
|
|
||||||
function onCameraCaptureButtonClick () {
|
|
||||||
// Make sure user can't click more than once
|
|
||||||
if (this.getAttribute('clicked') === '1') {
|
|
||||||
return false;
|
|
||||||
} else {
|
|
||||||
this.setAttribute('clicked', '1');
|
|
||||||
}
|
|
||||||
captureAction();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* When Cancel button is clicked, destroy camera preview and return with error callback
|
|
||||||
*/
|
|
||||||
function onCameraCancelButtonClick () {
|
|
||||||
// Make sure user can't click more than once
|
|
||||||
if (this.getAttribute('clicked') === '1') {
|
|
||||||
return false;
|
|
||||||
} else {
|
|
||||||
this.setAttribute('clicked', '1');
|
|
||||||
}
|
|
||||||
destroyCameraPreview();
|
|
||||||
errorCallback('no image selected');
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* When the phone orientation change, get the event and change camera preview rotation
|
|
||||||
* @param {Object} e - SimpleOrientationSensorOrientationChangedEventArgs
|
|
||||||
*/
|
|
||||||
function onOrientationChange (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
|
|
||||||
*/
|
|
||||||
function orientationToRotation (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
|
|
||||||
*/
|
|
||||||
function setPreviewRotation (orientation) {
|
|
||||||
capture.setPreviewRotation(orientationToRotation(orientation));
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
|
||||||
createCameraUI();
|
|
||||||
startCameraPreview();
|
|
||||||
} catch (ex) {
|
|
||||||
errorCallback(ex);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function takePictureFromCameraWindows (successCallback, errorCallback, args) {
|
|
||||||
const destinationType = args[1];
|
|
||||||
const targetWidth = args[3];
|
|
||||||
const targetHeight = args[4];
|
|
||||||
const encodingType = args[5];
|
|
||||||
const allowCrop = !!args[7];
|
|
||||||
const saveToPhotoAlbum = args[9];
|
|
||||||
const WMCapture = Windows.Media.Capture;
|
|
||||||
const cameraCaptureUI = new WMCapture.CameraCaptureUI();
|
|
||||||
|
|
||||||
cameraCaptureUI.photoSettings.allowCropping = allowCrop;
|
|
||||||
|
|
||||||
if (encodingType === Camera.EncodingType.PNG) {
|
|
||||||
cameraCaptureUI.photoSettings.format = WMCapture.CameraCaptureUIPhotoFormat.png;
|
|
||||||
} else {
|
|
||||||
cameraCaptureUI.photoSettings.format = WMCapture.CameraCaptureUIPhotoFormat.jpeg;
|
|
||||||
}
|
|
||||||
|
|
||||||
// decide which max pixels should be supported by targetWidth or targetHeight.
|
|
||||||
let maxRes = null;
|
|
||||||
const UIMaxRes = WMCapture.CameraCaptureUIMaxPhotoResolution;
|
|
||||||
const totalPixels = targetWidth * targetHeight;
|
|
||||||
|
|
||||||
if (targetWidth === -1 && targetHeight === -1) {
|
|
||||||
maxRes = UIMaxRes.highestAvailable;
|
|
||||||
// Temp fix for CB-10539
|
|
||||||
/* else if (totalPixels <= 320 * 240) {
|
|
||||||
maxRes = UIMaxRes.verySmallQvga;
|
|
||||||
} */
|
|
||||||
} else if (totalPixels <= 640 * 480) {
|
|
||||||
maxRes = UIMaxRes.smallVga;
|
|
||||||
} else if (totalPixels <= 1024 * 768) {
|
|
||||||
maxRes = UIMaxRes.mediumXga;
|
|
||||||
} else if (totalPixels <= 3 * 1000 * 1000) {
|
|
||||||
maxRes = UIMaxRes.large3M;
|
|
||||||
} else if (totalPixels <= 5 * 1000 * 1000) {
|
|
||||||
maxRes = UIMaxRes.veryLarge5M;
|
|
||||||
} else {
|
|
||||||
maxRes = UIMaxRes.highestAvailable;
|
|
||||||
}
|
|
||||||
|
|
||||||
cameraCaptureUI.photoSettings.maxResolution = maxRes;
|
|
||||||
|
|
||||||
let cameraPicture;
|
|
||||||
|
|
||||||
// define focus handler for windows phone 10.0
|
|
||||||
const savePhotoOnFocus = function () {
|
|
||||||
window.removeEventListener('focus', savePhotoOnFocus);
|
|
||||||
// call only when the app is in focus again
|
|
||||||
savePhoto(cameraPicture, {
|
|
||||||
destinationType,
|
|
||||||
targetHeight,
|
|
||||||
targetWidth,
|
|
||||||
encodingType,
|
|
||||||
saveToPhotoAlbum
|
|
||||||
}, successCallback, errorCallback);
|
|
||||||
};
|
|
||||||
|
|
||||||
// if windows phone 10, add and delete focus eventHandler to capture the focus back from cameraUI to app
|
|
||||||
if (navigator.appVersion.indexOf('Windows Phone 10.0') >= 0) {
|
|
||||||
window.addEventListener('focus', savePhotoOnFocus);
|
|
||||||
}
|
|
||||||
|
|
||||||
cameraCaptureUI.captureFileAsync(WMCapture.CameraCaptureUIMode.photo).done(function (picture) {
|
|
||||||
if (!picture) {
|
|
||||||
errorCallback("User didn't capture a photo.");
|
|
||||||
// Remove the focus handler if present
|
|
||||||
window.removeEventListener('focus', savePhotoOnFocus);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
cameraPicture = picture;
|
|
||||||
|
|
||||||
// If not windows 10, call savePhoto() now. If windows 10, wait for the app to be in focus again
|
|
||||||
if (navigator.appVersion.indexOf('Windows Phone 10.0') < 0) {
|
|
||||||
savePhoto(cameraPicture, {
|
|
||||||
destinationType,
|
|
||||||
targetHeight,
|
|
||||||
targetWidth,
|
|
||||||
encodingType,
|
|
||||||
saveToPhotoAlbum
|
|
||||||
}, successCallback, errorCallback);
|
|
||||||
}
|
|
||||||
}, function () {
|
|
||||||
errorCallback('Fail to capture a photo.');
|
|
||||||
window.removeEventListener('focus', savePhotoOnFocus);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
function savePhoto (picture, options, successCallback, errorCallback) {
|
|
||||||
// success callback for capture operation
|
|
||||||
const success = function (picture) {
|
|
||||||
if (options.destinationType === Camera.DestinationType.FILE_URI) {
|
|
||||||
if (options.targetHeight > 0 && options.targetWidth > 0) {
|
|
||||||
resizeImage(successCallback, errorCallback, picture, options.targetWidth, options.targetHeight, options.encodingType);
|
|
||||||
} else {
|
|
||||||
// CB-11714: check if target content-type is PNG to just rename as *.jpg since camera is captured as JPEG
|
|
||||||
if (options.encodingType === Camera.EncodingType.PNG) {
|
|
||||||
picture.name = picture.name.replace(/\.png$/, '.jpg');
|
|
||||||
}
|
|
||||||
|
|
||||||
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) {
|
|
||||||
const strBase64 = encodeToBase64String(buffer);
|
|
||||||
picture.deleteAsync().done(function () {
|
|
||||||
successCallback(strBase64);
|
|
||||||
}, function (err) {
|
|
||||||
errorCallback(err);
|
|
||||||
});
|
|
||||||
}, errorCallback);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
if (!options.saveToPhotoAlbum) {
|
|
||||||
success(picture);
|
|
||||||
} else {
|
|
||||||
const savePicker = new Windows.Storage.Pickers.FileSavePicker();
|
|
||||||
const 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;
|
|
||||||
|
|
||||||
if (options.encodingType === Camera.EncodingType.PNG) {
|
|
||||||
savePicker.fileTypeChoices.insert('PNG', ['.png']);
|
|
||||||
savePicker.suggestedFileName = 'photo.png';
|
|
||||||
} else {
|
|
||||||
savePicker.fileTypeChoices.insert('JPEG', ['.jpg']);
|
|
||||||
savePicker.suggestedFileName = 'photo.jpg';
|
|
||||||
}
|
|
||||||
|
|
||||||
// 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
|
|
||||||
*/
|
|
||||||
const fileSaveHandler = function (eventArgs) {
|
|
||||||
if (eventArgs.kind === Windows.ApplicationModel.Activation.ActivationKind.pickSaveFileContinuation) {
|
|
||||||
const file = eventArgs.file;
|
|
||||||
saveFile(file);
|
|
||||||
webUIApp.removeEventListener('activated', fileSaveHandler);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
webUIApp.addEventListener('activated', fileSaveHandler);
|
|
||||||
savePicker.pickSaveFileAndContinue();
|
|
||||||
} else {
|
|
||||||
savePicker.pickSaveFileAsync()
|
|
||||||
.done(saveFile, errorCallback);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
require('cordova/exec/proxy').add('Camera', module.exports);
|
|
@ -19,7 +19,7 @@
|
|||||||
*
|
*
|
||||||
*/
|
*/
|
||||||
|
|
||||||
/* globals Camera, resolveLocalFileSystemURL, FileEntry, CameraPopoverOptions, LocalFileSystem, MSApp */
|
/* globals Camera, resolveLocalFileSystemURL, FileEntry, CameraPopoverOptions, LocalFileSystem */
|
||||||
/* eslint-env jasmine */
|
/* eslint-env jasmine */
|
||||||
|
|
||||||
exports.defineAutoTests = function () {
|
exports.defineAutoTests = function () {
|
||||||
@ -433,15 +433,7 @@ exports.defineManualTests = function (contentEl, createActionButton) {
|
|||||||
'</p><div id="remove"></div>' +
|
'</p><div id="remove"></div>' +
|
||||||
'Expected result: Remove image from library.<br>Status box will show "FileEntry.remove success:["OK"]';
|
'Expected result: Remove image from library.<br>Status box will show "FileEntry.remove success:["OK"]';
|
||||||
|
|
||||||
// We need to wrap this code due to Windows security restrictions
|
contentEl.innerHTML = info_div + options_div + getpicture_div + test_procedure + inputs_div + actions_div;
|
||||||
// see http://msdn.microsoft.com/en-us/library/windows/apps/hh465380.aspx#differences for details
|
|
||||||
if (window.MSApp && window.MSApp.execUnsafeLocalFunction) {
|
|
||||||
MSApp.execUnsafeLocalFunction(function () {
|
|
||||||
contentEl.innerHTML = info_div + options_div + getpicture_div + test_procedure + inputs_div + actions_div;
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
contentEl.innerHTML = info_div + options_div + getpicture_div + test_procedure + inputs_div + actions_div;
|
|
||||||
}
|
|
||||||
|
|
||||||
const elements = document.getElementsByClassName('testInputTag');
|
const elements = document.getElementsByClassName('testInputTag');
|
||||||
const listener = function (e) {
|
const listener = function (e) {
|
||||||
|
@ -114,14 +114,8 @@ for (const key in Camera) {
|
|||||||
* __Supported Platforms__
|
* __Supported Platforms__
|
||||||
*
|
*
|
||||||
* - Android
|
* - Android
|
||||||
* - BlackBerry
|
|
||||||
* - Browser
|
* - Browser
|
||||||
* - Firefox
|
|
||||||
* - FireOS
|
|
||||||
* - iOS
|
* - iOS
|
||||||
* - Windows
|
|
||||||
* - WP8
|
|
||||||
* - Ubuntu
|
|
||||||
*
|
*
|
||||||
* More examples [here](#camera-getPicture-examples). Quirks [here](#camera-getPicture-quirks).
|
* More examples [here](#camera-getPicture-examples). Quirks [here](#camera-getPicture-quirks).
|
||||||
*
|
*
|
||||||
|
Loading…
Reference in New Issue
Block a user