arnaucube
arnaucube/openEventsPlatformAppPublic Self-hosted repository
openEventsPlatformApp/plugins/cordova-plugin-camera/src/windows/CameraProxy.js
plugins/cordova-plugin-camera/src/windows/CameraProxy.jsRaw
  1/*
  2 *
  3 * Licensed to the Apache Software Foundation (ASF) under one
  4 * or more contributor license agreements.  See the NOTICE file
  5 * distributed with this work for additional information
  6 * regarding copyright ownership.  The ASF licenses this file
  7 * to you under the Apache License, Version 2.0 (the
  8 * "License"); you may not use this file except in compliance
  9 * with the License.  You may obtain a copy of the License at
 10 *
 11 *   http://www.apache.org/licenses/LICENSE-2.0
 12 *
 13 * Unless required by applicable law or agreed to in writing,
 14 * software distributed under the License is distributed on an
 15 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
 16 * KIND, either express or implied.  See the License for the
 17 * specific language governing permissions and limitations
 18 * under the License.
 19 *
 20*/
 21
 22/*jshint unused:true, undef:true, browser:true */
 23/*global Windows:true, URL:true, module:true, require:true, WinJS:true */
 24
 25
 26var Camera = require('./Camera');
 27
 28
 29var getAppData = function () {
 30    return Windows.Storage.ApplicationData.current;
 31};
 32var encodeToBase64String = function (buffer) {
 33    return Windows.Security.Cryptography.CryptographicBuffer.encodeToBase64String(buffer);
 34};
 35var OptUnique = Windows.Storage.CreationCollisionOption.generateUniqueName;
 36var CapMSType = Windows.Media.Capture.MediaStreamType;
 37var webUIApp = Windows.UI.WebUI.WebUIApplication;
 38var fileIO = Windows.Storage.FileIO;
 39var pickerLocId = Windows.Storage.Pickers.PickerLocationId;
 40
 41module.exports = {
 42
 43    // args will contain :
 44    //  ...  it is an array, so be careful
 45    // 0 quality:50,
 46    // 1 destinationType:Camera.DestinationType.FILE_URI,
 47    // 2 sourceType:Camera.PictureSourceType.CAMERA,
 48    // 3 targetWidth:-1,
 49    // 4 targetHeight:-1,
 50    // 5 encodingType:Camera.EncodingType.JPEG,
 51    // 6 mediaType:Camera.MediaType.PICTURE,
 52    // 7 allowEdit:false,
 53    // 8 correctOrientation:false,
 54    // 9 saveToPhotoAlbum:false,
 55    // 10 popoverOptions:null
 56    // 11 cameraDirection:0
 57
 58    takePicture: function (successCallback, errorCallback, args) {
 59        var sourceType = args[2];
 60
 61        if (sourceType != Camera.PictureSourceType.CAMERA) {
 62            takePictureFromFile(successCallback, errorCallback, args);
 63        } else {
 64            takePictureFromCamera(successCallback, errorCallback, args);
 65        }
 66    }
 67};
 68
 69// https://msdn.microsoft.com/en-us/library/windows/apps/ff462087(v=vs.105).aspx
 70var windowsVideoContainers = [".avi", ".flv", ".asx", ".asf", ".mov", ".mp4", ".mpg", ".rm", ".srt", ".swf", ".wmv", ".vob"];
 71var windowsPhoneVideoContainers =  [".avi", ".3gp", ".3g2", ".wmv", ".3gp", ".3g2", ".mp4", ".m4v"];
 72
 73// Default aspect ratio 1.78 (16:9 hd video standard)
 74var DEFAULT_ASPECT_RATIO = '1.8';
 75
 76// Highest possible z-index supported across browsers. Anything used above is converted to this value.
 77var HIGHEST_POSSIBLE_Z_INDEX = 2147483647;
 78
 79// Resize method
 80function resizeImage(successCallback, errorCallback, file, targetWidth, targetHeight, encodingType) {
 81    var tempPhotoFileName = "";
 82    var targetContentType = "";
 83
 84    if (encodingType == Camera.EncodingType.PNG) {
 85        tempPhotoFileName = "camera_cordova_temp_return.png";
 86        targetContentType = "image/png";
 87    } else {
 88        tempPhotoFileName = "camera_cordova_temp_return.jpg";
 89        targetContentType = "image/jpeg";
 90    }
 91
 92    var storageFolder = getAppData().localFolder;
 93    file.copyAsync(storageFolder, file.name, Windows.Storage.NameCollisionOption.replaceExisting)
 94        .then(function (storageFile) {
 95            return fileIO.readBufferAsync(storageFile);
 96        })
 97        .then(function(buffer) {
 98            var strBase64 = encodeToBase64String(buffer);
 99            var imageData = "data:" + file.contentType + ";base64," + strBase64;
100            var image = new Image();
101            image.src = imageData;
102            image.onload = function() {
103                var ratio = Math.min(targetWidth / this.width, targetHeight / this.height);
104                var imageWidth = ratio * this.width;
105                var imageHeight = ratio * this.height;
106
107                var canvas = document.createElement('canvas');
108                var storageFileName;
109
110                canvas.width = imageWidth;
111                canvas.height = imageHeight;
112
113                canvas.getContext("2d").drawImage(this, 0, 0, imageWidth, imageHeight);
114
115                var fileContent = canvas.toDataURL(targetContentType).split(',')[1];
116
117                var storageFolder = getAppData().localFolder;
118
119                storageFolder.createFileAsync(tempPhotoFileName, OptUnique)
120                    .then(function (storagefile) {
121                        var content = Windows.Security.Cryptography.CryptographicBuffer.decodeFromBase64String(fileContent);
122                        storageFileName = storagefile.name;
123                        return fileIO.writeBufferAsync(storagefile, content);
124                    })
125                    .done(function () {
126                        successCallback("ms-appdata:///local/" + storageFileName);
127                    }, errorCallback);
128            };
129        })
130        .done(null, function(err) {
131            errorCallback(err);
132        }
133    );
134}
135
136// Because of asynchronous method, so let the successCallback be called in it.
137function resizeImageBase64(successCallback, errorCallback, file, targetWidth, targetHeight) {
138    fileIO.readBufferAsync(file).done( function(buffer) {
139        var strBase64 = encodeToBase64String(buffer);
140        var imageData = "data:" + file.contentType + ";base64," + strBase64;
141
142        var image = new Image();
143        image.src = imageData;
144
145        image.onload = function() {
146            var ratio = Math.min(targetWidth / this.width, targetHeight / this.height);
147            var imageWidth = ratio * this.width;
148            var imageHeight = ratio * this.height;
149            var canvas = document.createElement('canvas');
150
151            canvas.width = imageWidth;
152            canvas.height = imageHeight;
153
154            var ctx = canvas.getContext("2d");
155            ctx.drawImage(this, 0, 0, imageWidth, imageHeight);
156
157            // The resized file ready for upload
158            var finalFile = canvas.toDataURL(file.contentType);
159
160            // Remove the prefix such as "data:" + contentType + ";base64," , in order to meet the Cordova API.
161            var arr = finalFile.split(",");
162            var newStr = finalFile.substr(arr[0].length + 1);
163            successCallback(newStr);
164        };
165    }, function(err) { errorCallback(err); });
166}
167
168function takePictureFromFile(successCallback, errorCallback, args) {
169    // Detect Windows Phone
170    if (navigator.appVersion.indexOf('Windows Phone 8.1') >= 0) {
171        takePictureFromFileWP(successCallback, errorCallback, args);
172    } else {
173        takePictureFromFileWindows(successCallback, errorCallback, args);
174    }
175}
176
177function takePictureFromFileWP(successCallback, errorCallback, args) {
178    var mediaType = args[6],
179        destinationType = args[1],
180        targetWidth = args[3],
181        targetHeight = args[4],
182        encodingType = args[5];
183
184    /*
185        Need to add and remove an event listener to catch activation state
186        Using FileOpenPicker will suspend the app and it's required to catch the PickSingleFileAndContinue
187        https://msdn.microsoft.com/en-us/library/windows/apps/xaml/dn631755.aspx
188    */
189    var filePickerActivationHandler = function(eventArgs) {
190        if (eventArgs.kind === Windows.ApplicationModel.Activation.ActivationKind.pickFileContinuation) {
191            var file = eventArgs.files[0];
192            if (!file) {
193                errorCallback("User didn't choose a file.");
194                webUIApp.removeEventListener("activated", filePickerActivationHandler);
195                return;
196            }
197            if (destinationType == Camera.DestinationType.FILE_URI || destinationType == Camera.DestinationType.NATIVE_URI) {
198                if (targetHeight > 0 && targetWidth > 0) {
199                    resizeImage(successCallback, errorCallback, file, targetWidth, targetHeight, encodingType);
200                }
201                else {
202                    var storageFolder = getAppData().localFolder;
203                    file.copyAsync(storageFolder, file.name, Windows.Storage.NameCollisionOption.replaceExisting).done(function (storageFile) {
204                        if(destinationType == Camera.DestinationType.NATIVE_URI) {
205                            successCallback("ms-appdata:///local/" + storageFile.name);
206                        }
207                        else {
208                            successCallback(URL.createObjectURL(storageFile));
209                        }
210                    }, function () {
211                        errorCallback("Can't access localStorage folder.");
212                    });
213                }
214            }
215            else {
216                if (targetHeight > 0 && targetWidth > 0) {
217                    resizeImageBase64(successCallback, errorCallback, file, targetWidth, targetHeight);
218                } else {
219                    fileIO.readBufferAsync(file).done(function (buffer) {
220                        var strBase64 =encodeToBase64String(buffer);
221                        successCallback(strBase64);
222                    }, errorCallback);
223                }
224            }
225            webUIApp.removeEventListener("activated", filePickerActivationHandler);
226        }
227    };
228
229    var fileOpenPicker = new Windows.Storage.Pickers.FileOpenPicker();
230    if (mediaType == Camera.MediaType.PICTURE) {
231        fileOpenPicker.fileTypeFilter.replaceAll([".png", ".jpg", ".jpeg"]);
232        fileOpenPicker.suggestedStartLocation = pickerLocId.picturesLibrary;
233    }
234    else if (mediaType == Camera.MediaType.VIDEO) {
235        fileOpenPicker.fileTypeFilter.replaceAll(windowsPhoneVideoContainers);
236        fileOpenPicker.suggestedStartLocation = pickerLocId.videosLibrary;
237    }
238    else {
239        fileOpenPicker.fileTypeFilter.replaceAll(["*"]);
240        fileOpenPicker.suggestedStartLocation = pickerLocId.documentsLibrary;
241    }
242
243    webUIApp.addEventListener("activated", filePickerActivationHandler);
244    fileOpenPicker.pickSingleFileAndContinue();
245}
246
247function takePictureFromFileWindows(successCallback, errorCallback, args) {
248    var mediaType = args[6],
249        destinationType = args[1],
250        targetWidth = args[3],
251        targetHeight = args[4],
252        encodingType = args[5];
253
254    var fileOpenPicker = new Windows.Storage.Pickers.FileOpenPicker();
255    if (mediaType == Camera.MediaType.PICTURE) {
256        fileOpenPicker.fileTypeFilter.replaceAll([".png", ".jpg", ".jpeg"]);
257        fileOpenPicker.suggestedStartLocation = pickerLocId.picturesLibrary;
258    }
259    else if (mediaType == Camera.MediaType.VIDEO) {
260        fileOpenPicker.fileTypeFilter.replaceAll(windowsVideoContainers);
261        fileOpenPicker.suggestedStartLocation = pickerLocId.videosLibrary;
262    }
263    else {
264        fileOpenPicker.fileTypeFilter.replaceAll(["*"]);
265        fileOpenPicker.suggestedStartLocation = pickerLocId.documentsLibrary;
266    }
267
268    fileOpenPicker.pickSingleFileAsync().done(function (file) {
269        if (!file) {
270            errorCallback("User didn't choose a file.");
271            return;
272        }
273        if (destinationType == Camera.DestinationType.FILE_URI || destinationType == Camera.DestinationType.NATIVE_URI) {
274            if (targetHeight > 0 && targetWidth > 0) {
275                resizeImage(successCallback, errorCallback, file, targetWidth, targetHeight, encodingType);
276            }
277            else {
278                var storageFolder = getAppData().localFolder;
279                file.copyAsync(storageFolder, file.name, Windows.Storage.NameCollisionOption.replaceExisting).done(function (storageFile) {
280                        if(destinationType == Camera.DestinationType.NATIVE_URI) {
281                            successCallback("ms-appdata:///local/" + storageFile.name);
282                        }
283                        else {
284                            successCallback(URL.createObjectURL(storageFile));
285                        }
286                }, function () {
287                    errorCallback("Can't access localStorage folder.");
288                });
289            }
290        }
291        else {
292            if (targetHeight > 0 && targetWidth > 0) {
293                resizeImageBase64(successCallback, errorCallback, file, targetWidth, targetHeight);
294            } else {
295                fileIO.readBufferAsync(file).done(function (buffer) {
296                    var strBase64 =encodeToBase64String(buffer);
297                    successCallback(strBase64);
298                }, errorCallback);
299            }
300        }
301    }, function () {
302        errorCallback("User didn't choose a file.");
303    });
304}
305
306function takePictureFromCamera(successCallback, errorCallback, args) {
307    // Check if necessary API available
308    if (!Windows.Media.Capture.CameraCaptureUI) {
309        takePictureFromCameraWP(successCallback, errorCallback, args);
310    } else {
311        takePictureFromCameraWindows(successCallback, errorCallback, args);
312    }
313}
314
315function takePictureFromCameraWP(successCallback, errorCallback, args) {
316    // We are running on WP8.1 which lacks CameraCaptureUI class
317    // so we need to use MediaCapture class instead and implement custom UI for camera
318    var destinationType = args[1],
319        targetWidth = args[3],
320        targetHeight = args[4],
321        encodingType = args[5],
322        saveToPhotoAlbum = args[9],
323        cameraDirection = args[11],
324        capturePreview = null,
325        cameraCaptureButton = null,
326        cameraCancelButton = null,
327        capture = null,
328        captureSettings = null,
329        CaptureNS = Windows.Media.Capture,
330        sensor = null;
331
332    function createCameraUI() {
333        // create style for take and cancel buttons
334        var 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;";
335
336        // Create fullscreen preview
337        // z-order style element for capturePreview and cameraCancelButton elts
338        // is necessary to avoid overriding by another page elements, -1 sometimes is not enough
339        capturePreview = document.createElement("video");
340        capturePreview.style.cssText = "position: fixed; left: 0; top: 0; width: 100%; height: 100%; z-index: " + (HIGHEST_POSSIBLE_Z_INDEX - 1) + ";";
341
342        // Create capture button
343        cameraCaptureButton = document.createElement("button");
344        cameraCaptureButton.innerText = "Take";
345        cameraCaptureButton.style.cssText = buttonStyle + "position: fixed; left: 0; bottom: 0; margin: 20px; z-index: " + HIGHEST_POSSIBLE_Z_INDEX + ";";
346
347        // Create cancel button
348        cameraCancelButton = document.createElement("button");
349        cameraCancelButton.innerText = "Cancel";
350        cameraCancelButton.style.cssText = buttonStyle + "position: fixed; right: 0; bottom: 0; margin: 20px; z-index: " + HIGHEST_POSSIBLE_Z_INDEX + ";";
351
352        capture = new CaptureNS.MediaCapture();
353
354        captureSettings = new CaptureNS.MediaCaptureInitializationSettings();
355        captureSettings.streamingCaptureMode = CaptureNS.StreamingCaptureMode.video;
356    }
357
358    function continueVideoOnFocus() {
359        // if preview is defined it would be stuck, play it
360        if (capturePreview) {
361            capturePreview.play();
362        }
363    }
364
365    function startCameraPreview() {
366        // Search for available camera devices
367        // This is necessary to detect which camera (front or back) we should use
368        var DeviceEnum = Windows.Devices.Enumeration;
369        var expectedPanel = cameraDirection === 1 ? DeviceEnum.Panel.front : DeviceEnum.Panel.back;
370
371        // Add focus event handler to capture the event when user suspends the app and comes back while the preview is on
372        window.addEventListener("focus", continueVideoOnFocus);
373
374        DeviceEnum.DeviceInformation.findAllAsync(DeviceEnum.DeviceClass.videoCapture).then(function (devices) {
375            if (devices.length <= 0) {
376                destroyCameraPreview();
377                errorCallback('Camera not found');
378                return;
379            }
380
381            devices.forEach(function(currDev) {
382                if (currDev.enclosureLocation.panel && currDev.enclosureLocation.panel == expectedPanel) {
383                    captureSettings.videoDeviceId = currDev.id;
384                }
385            });
386
387            captureSettings.photoCaptureSource = Windows.Media.Capture.PhotoCaptureSource.photo;
388
389            return capture.initializeAsync(captureSettings);
390        }).then(function () {
391
392            // create focus control if available
393            var VideoDeviceController = capture.videoDeviceController;
394            var FocusControl = VideoDeviceController.focusControl;
395
396            if (FocusControl.supported === true) {
397                capturePreview.addEventListener('click', function () {
398                    // Make sure function isn't called again before previous focus is completed
399                    if (this.getAttribute('clicked') === '1') {
400                        return false;
401                    } else {
402                        this.setAttribute('clicked', '1');
403                    }
404                    var preset = Windows.Media.Devices.FocusPreset.autoNormal;
405                    var parent = this;
406                    FocusControl.setPresetAsync(preset).done(function () {
407                        // set the clicked attribute back to '0' to allow focus again
408                        parent.setAttribute('clicked', '0');
409                    });
410                });
411            }
412
413            // msdn.microsoft.com/en-us/library/windows/apps/hh452807.aspx
414            capturePreview.msZoom = true;
415            capturePreview.src = URL.createObjectURL(capture);
416            capturePreview.play();
417
418            // Bind events to controls
419            sensor = Windows.Devices.Sensors.SimpleOrientationSensor.getDefault();
420            if (sensor !== null) {
421                sensor.addEventListener("orientationchanged", onOrientationChange);
422            }
423
424            // add click events to capture and cancel buttons
425            cameraCaptureButton.addEventListener('click', onCameraCaptureButtonClick);
426            cameraCancelButton.addEventListener('click', onCameraCancelButtonClick);
427
428            // Change default orientation
429            if (sensor) {
430                setPreviewRotation(sensor.getCurrentOrientation());
431            } else {
432                setPreviewRotation(Windows.Graphics.Display.DisplayInformation.getForCurrentView().currentOrientation);
433            }
434
435            // Get available aspect ratios
436            var aspectRatios = getAspectRatios(capture);
437
438            // Couldn't find a good ratio
439            if (aspectRatios.length === 0) {
440                destroyCameraPreview();
441                errorCallback('There\'s not a good aspect ratio available');
442                return;
443            }
444
445            // add elements to body
446            document.body.appendChild(capturePreview);
447            document.body.appendChild(cameraCaptureButton);
448            document.body.appendChild(cameraCancelButton);
449
450            if (aspectRatios.indexOf(DEFAULT_ASPECT_RATIO) > -1) {
451                return setAspectRatio(capture, DEFAULT_ASPECT_RATIO);
452            } else {
453                // Doesn't support 16:9 - pick next best
454                return setAspectRatio(capture, aspectRatios[0]);
455            }
456        }).done(null, function (err) {
457            destroyCameraPreview();
458            errorCallback('Camera intitialization error ' + err);
459        });
460    }
461
462    function destroyCameraPreview() {
463        // If sensor is available, remove event listener
464        if (sensor !== null) {
465            sensor.removeEventListener('orientationchanged', onOrientationChange);
466        }
467
468        // Pause and dispose preview element
469        capturePreview.pause();
470        capturePreview.src = null;
471
472        // Remove event listeners from buttons
473        cameraCaptureButton.removeEventListener('click', onCameraCaptureButtonClick);
474        cameraCancelButton.removeEventListener('click', onCameraCancelButtonClick);
475
476        // Remove the focus event handler
477        window.removeEventListener("focus", continueVideoOnFocus);
478
479        // Remove elements
480        [capturePreview, cameraCaptureButton, cameraCancelButton].forEach(function (elem) {
481            if (elem /* && elem in document.body.childNodes */) {
482                document.body.removeChild(elem);
483            }
484        });
485
486        // Stop and dispose media capture manager
487        if (capture) {
488            capture.stopRecordAsync();
489            capture = null;
490        }
491    }
492
493    function captureAction() {
494
495        var encodingProperties,
496            fileName,
497            tempFolder = getAppData().temporaryFolder;
498
499        if (encodingType == Camera.EncodingType.PNG) {
500            fileName = 'photo.png';
501            encodingProperties = Windows.Media.MediaProperties.ImageEncodingProperties.createPng();
502        } else {
503            fileName = 'photo.jpg';
504            encodingProperties = Windows.Media.MediaProperties.ImageEncodingProperties.createJpeg();
505        }
506
507        tempFolder.createFileAsync(fileName, OptUnique)
508            .then(function(tempCapturedFile) {
509                return new WinJS.Promise(function (complete) {
510                    var photoStream = new Windows.Storage.Streams.InMemoryRandomAccessStream();
511                    var finalStream = new Windows.Storage.Streams.InMemoryRandomAccessStream();
512                    capture.capturePhotoToStreamAsync(encodingProperties, photoStream)
513                        .then(function() {
514                            return Windows.Graphics.Imaging.BitmapDecoder.createAsync(photoStream);
515                        })
516                        .then(function(dec) {
517                            finalStream.size = 0; // BitmapEncoder requires the output stream to be empty
518                            return Windows.Graphics.Imaging.BitmapEncoder.createForTranscodingAsync(finalStream, dec);
519                        })
520                        .then(function(enc) {
521                            // We need to rotate the photo wrt sensor orientation
522                            enc.bitmapTransform.rotation = orientationToRotation(sensor.getCurrentOrientation());
523                            return enc.flushAsync();
524                        })
525                        .then(function() {
526                            return tempCapturedFile.openAsync(Windows.Storage.FileAccessMode.readWrite);
527                        })
528                        .then(function(fileStream) {
529                            return Windows.Storage.Streams.RandomAccessStream.copyAndCloseAsync(finalStream, fileStream);
530                        })
531                        .done(function() {
532                            photoStream.close();
533                            finalStream.close();
534                            complete(tempCapturedFile);
535                        }, function() {
536                            photoStream.close();
537                            finalStream.close();
538                            throw new Error("An error has occured while capturing the photo.");
539                        });
540                });
541            })
542            .done(function(capturedFile) {
543                destroyCameraPreview();
544                savePhoto(capturedFile, {
545                    destinationType: destinationType,
546                    targetHeight: targetHeight,
547                    targetWidth: targetWidth,
548                    encodingType: encodingType,
549                    saveToPhotoAlbum: saveToPhotoAlbum
550                }, successCallback, errorCallback);
551            }, function(err) {
552                destroyCameraPreview();
553                errorCallback(err);
554            });
555    }
556
557    function getAspectRatios(capture) {
558        var videoDeviceController = capture.videoDeviceController;
559        var photoAspectRatios = videoDeviceController.getAvailableMediaStreamProperties(CapMSType.photo).map(function (element) {
560            return (element.width / element.height).toFixed(1);
561        }).filter(function (element, index, array) { return (index === array.indexOf(element)); });
562
563        var videoAspectRatios = videoDeviceController.getAvailableMediaStreamProperties(CapMSType.videoRecord).map(function (element) {
564            return (element.width / element.height).toFixed(1);
565        }).filter(function (element, index, array) { return (index === array.indexOf(element)); });
566
567        var videoPreviewAspectRatios = videoDeviceController.getAvailableMediaStreamProperties(CapMSType.videoPreview).map(function (element) {
568            return (element.width / element.height).toFixed(1);
569        }).filter(function (element, index, array) { return (index === array.indexOf(element)); });
570
571        var allAspectRatios = [].concat(photoAspectRatios, videoAspectRatios, videoPreviewAspectRatios);
572
573        var aspectObj = allAspectRatios.reduce(function (map, item) {
574            if (!map[item]) {
575                map[item] = 0;
576            }
577            map[item]++;
578            return map;
579        }, {});
580
581        return Object.keys(aspectObj).filter(function (k) {
582            return aspectObj[k] === 3;
583        });
584    }
585
586    function setAspectRatio(capture, aspect) {
587        // Max photo resolution with desired aspect ratio
588        var videoDeviceController = capture.videoDeviceController;
589        var photoResolution = videoDeviceController.getAvailableMediaStreamProperties(CapMSType.photo)
590            .filter(function (elem) {
591                return ((elem.width / elem.height).toFixed(1) === aspect);
592            })
593            .reduce(function (prop1, prop2) {
594                return (prop1.width * prop1.height) > (prop2.width * prop2.height) ? prop1 : prop2;
595            });
596
597        // Max video resolution with desired aspect ratio
598        var videoRecordResolution = videoDeviceController.getAvailableMediaStreamProperties(CapMSType.videoRecord)
599            .filter(function (elem) {
600                return ((elem.width / elem.height).toFixed(1) === aspect);
601            })
602            .reduce(function (prop1, prop2) {
603                return (prop1.width * prop1.height) > (prop2.width * prop2.height) ? prop1 : prop2;
604            });
605
606        // Max video preview resolution with desired aspect ratio
607        var videoPreviewResolution = videoDeviceController.getAvailableMediaStreamProperties(CapMSType.videoPreview)
608            .filter(function (elem) {
609                return ((elem.width / elem.height).toFixed(1) === aspect);
610            })
611            .reduce(function (prop1, prop2) {
612                return (prop1.width * prop1.height) > (prop2.width * prop2.height) ? prop1 : prop2;
613            });
614
615        return videoDeviceController.setMediaStreamPropertiesAsync(CapMSType.photo, photoResolution)
616            .then(function () {
617                return videoDeviceController.setMediaStreamPropertiesAsync(CapMSType.videoPreview, videoPreviewResolution);
618            })
619            .then(function () {
620                return videoDeviceController.setMediaStreamPropertiesAsync(CapMSType.videoRecord, videoRecordResolution);
621            });
622    }
623
624    /**
625     * When Capture button is clicked, try to capture a picture and return
626     */
627    function onCameraCaptureButtonClick() {
628        // Make sure user can't click more than once
629        if (this.getAttribute('clicked') === '1') {
630            return false;
631        } else {
632            this.setAttribute('clicked', '1');
633        }
634        captureAction();
635    }
636
637    /**
638     * When Cancel button is clicked, destroy camera preview and return with error callback
639     */
640    function onCameraCancelButtonClick() {
641        // Make sure user can't click more than once
642        if (this.getAttribute('clicked') === '1') {
643            return false;
644        } else {
645            this.setAttribute('clicked', '1');
646        }
647        destroyCameraPreview();
648        errorCallback('no image selected');
649    }
650
651    /**
652     * When the phone orientation change, get the event and change camera preview rotation
653     * @param  {Object} e - SimpleOrientationSensorOrientationChangedEventArgs
654     */
655    function onOrientationChange(e) {
656        setPreviewRotation(e.orientation);
657    }
658
659    /**
660     * Converts SimpleOrientation to a VideoRotation to remove difference between camera sensor orientation
661     * and video orientation
662     * @param  {number} orientation - Windows.Devices.Sensors.SimpleOrientation
663     * @return {number} - Windows.Media.Capture.VideoRotation
664     */
665    function orientationToRotation(orientation) {
666        // VideoRotation enumerable and BitmapRotation enumerable have the same values
667        // https://msdn.microsoft.com/en-us/library/windows/apps/windows.media.capture.videorotation.aspx
668        // https://msdn.microsoft.com/en-us/library/windows/apps/windows.graphics.imaging.bitmaprotation.aspx
669
670        switch (orientation) {
671            // portrait
672            case Windows.Devices.Sensors.SimpleOrientation.notRotated:
673                return Windows.Media.Capture.VideoRotation.clockwise90Degrees;
674            // landscape
675            case Windows.Devices.Sensors.SimpleOrientation.rotated90DegreesCounterclockwise:
676                return Windows.Media.Capture.VideoRotation.none;
677            // portrait-flipped (not supported by WinPhone Apps)
678            case Windows.Devices.Sensors.SimpleOrientation.rotated180DegreesCounterclockwise:
679                // Falling back to portrait default
680                return Windows.Media.Capture.VideoRotation.clockwise90Degrees;
681            // landscape-flipped
682            case Windows.Devices.Sensors.SimpleOrientation.rotated270DegreesCounterclockwise:
683                return Windows.Media.Capture.VideoRotation.clockwise180Degrees;
684            // faceup & facedown
685            default:
686                // Falling back to portrait default
687                return Windows.Media.Capture.VideoRotation.clockwise90Degrees;
688        }
689    }
690
691    /**
692     * Rotates the current MediaCapture's video
693     * @param {number} orientation - Windows.Devices.Sensors.SimpleOrientation
694     */
695    function setPreviewRotation(orientation) {
696        capture.setPreviewRotation(orientationToRotation(orientation));
697    }
698
699    try {
700        createCameraUI();
701        startCameraPreview();
702    } catch (ex) {
703        errorCallback(ex);
704    }
705}
706
707function takePictureFromCameraWindows(successCallback, errorCallback, args) {
708    var destinationType = args[1],
709        targetWidth = args[3],
710        targetHeight = args[4],
711        encodingType = args[5],
712        allowCrop = !!args[7],
713        saveToPhotoAlbum = args[9],
714        WMCapture = Windows.Media.Capture,
715        cameraCaptureUI = new WMCapture.CameraCaptureUI();
716
717    cameraCaptureUI.photoSettings.allowCropping = allowCrop;
718
719    if (encodingType == Camera.EncodingType.PNG) {
720        cameraCaptureUI.photoSettings.format = WMCapture.CameraCaptureUIPhotoFormat.png;
721    } else {
722        cameraCaptureUI.photoSettings.format = WMCapture.CameraCaptureUIPhotoFormat.jpeg;
723    }
724
725    // decide which max pixels should be supported by targetWidth or targetHeight.
726    var maxRes = null;
727    var UIMaxRes = WMCapture.CameraCaptureUIMaxPhotoResolution;
728    var totalPixels = targetWidth * targetHeight;
729
730    if (targetWidth == -1 && targetHeight == -1) {
731        maxRes = UIMaxRes.highestAvailable;
732    }
733    // Temp fix for CB-10539
734    /*else if (totalPixels <= 320 * 240) {
735        maxRes = UIMaxRes.verySmallQvga;
736    }*/
737    else if (totalPixels <= 640 * 480) {
738        maxRes = UIMaxRes.smallVga;
739    } else if (totalPixels <= 1024 * 768) {
740        maxRes = UIMaxRes.mediumXga;
741    } else if (totalPixels <= 3 * 1000 * 1000) {
742        maxRes = UIMaxRes.large3M;
743    } else if (totalPixels <= 5 * 1000 * 1000) {
744        maxRes = UIMaxRes.veryLarge5M;
745    } else {
746        maxRes = UIMaxRes.highestAvailable;
747    }
748
749    cameraCaptureUI.photoSettings.maxResolution = maxRes;
750
751    var cameraPicture;
752
753    // define focus handler for windows phone 10.0
754    var savePhotoOnFocus = function () {
755        window.removeEventListener("focus", savePhotoOnFocus);
756        // call only when the app is in focus again
757        savePhoto(cameraPicture, {
758            destinationType: destinationType,
759            targetHeight: targetHeight,
760            targetWidth: targetWidth,
761            encodingType: encodingType,
762            saveToPhotoAlbum: saveToPhotoAlbum
763        }, successCallback, errorCallback);
764    };
765
766    // if windows phone 10, add and delete focus eventHandler to capture the focus back from cameraUI to app
767    if (navigator.appVersion.indexOf('Windows Phone 10.0') >= 0) {
768        window.addEventListener("focus", savePhotoOnFocus);
769    }
770
771    cameraCaptureUI.captureFileAsync(WMCapture.CameraCaptureUIMode.photo).done(function (picture) {
772        if (!picture) {
773            errorCallback("User didn't capture a photo.");
774            // Remove the focus handler if present
775            window.removeEventListener("focus", savePhotoOnFocus);
776            return;
777        }
778        cameraPicture = picture;
779
780        // If not windows 10, call savePhoto() now. If windows 10, wait for the app to be in focus again
781        if (navigator.appVersion.indexOf('Windows Phone 10.0') < 0) {
782            savePhoto(cameraPicture, {
783                destinationType: destinationType,
784                targetHeight: targetHeight,
785                targetWidth: targetWidth,
786                encodingType: encodingType,
787                saveToPhotoAlbum: saveToPhotoAlbum
788            }, successCallback, errorCallback);
789        }
790    }, function () {
791        errorCallback("Fail to capture a photo.");
792        window.removeEventListener("focus", savePhotoOnFocus);
793    });
794}
795
796function savePhoto(picture, options, successCallback, errorCallback) {
797    // success callback for capture operation
798    var success = function(picture) {
799        if (options.destinationType == Camera.DestinationType.FILE_URI || options.destinationType == Camera.DestinationType.NATIVE_URI) {
800            if (options.targetHeight > 0 && options.targetWidth > 0) {
801                resizeImage(successCallback, errorCallback, picture, options.targetWidth, options.targetHeight, options.encodingType);
802            } else {
803                picture.copyAsync(getAppData().localFolder, picture.name, OptUnique).done(function (copiedFile) {
804                    successCallback("ms-appdata:///local/" + copiedFile.name);
805                },errorCallback);
806            }
807        } else {
808            if (options.targetHeight > 0 && options.targetWidth > 0) {
809                resizeImageBase64(successCallback, errorCallback, picture, options.targetWidth, options.targetHeight);
810            } else {
811                fileIO.readBufferAsync(picture).done(function(buffer) {
812                    var strBase64 = encodeToBase64String(buffer);
813                    picture.deleteAsync().done(function() {
814                        successCallback(strBase64);
815                    }, function(err) {
816                        errorCallback(err);
817                    });
818                }, errorCallback);
819            }
820        }
821    };
822
823    if (!options.saveToPhotoAlbum) {
824        success(picture);
825        return;
826    } else {
827        var savePicker = new Windows.Storage.Pickers.FileSavePicker();
828        var saveFile = function(file) {
829            if (file) {
830                // Prevent updates to the remote version of the file until we're done
831                Windows.Storage.CachedFileManager.deferUpdates(file);
832                picture.moveAndReplaceAsync(file)
833                    .then(function() {
834                        // Let Windows know that we're finished changing the file so
835                        // the other app can update the remote version of the file.
836                        return Windows.Storage.CachedFileManager.completeUpdatesAsync(file);
837                    })
838                    .done(function(updateStatus) {
839                        if (updateStatus === Windows.Storage.Provider.FileUpdateStatus.complete) {
840                            success(picture);
841                        } else {
842                            errorCallback("File update status is not complete.");
843                        }
844                    }, errorCallback);
845            } else {
846                errorCallback("Failed to select a file.");
847            }
848        };
849        savePicker.suggestedStartLocation = pickerLocId.picturesLibrary;
850
851        if (options.encodingType === Camera.EncodingType.PNG) {
852            savePicker.fileTypeChoices.insert("PNG", [".png"]);
853            savePicker.suggestedFileName = "photo.png";
854        } else {
855            savePicker.fileTypeChoices.insert("JPEG", [".jpg"]);
856            savePicker.suggestedFileName = "photo.jpg";
857        }
858
859        // If Windows Phone 8.1 use pickSaveFileAndContinue()
860        if (navigator.appVersion.indexOf('Windows Phone 8.1') >= 0) {
861            /*
862                Need to add and remove an event listener to catch activation state
863                Using FileSavePicker will suspend the app and it's required to catch the pickSaveFileContinuation
864                https://msdn.microsoft.com/en-us/library/windows/apps/xaml/dn631755.aspx
865            */
866            var fileSaveHandler = function(eventArgs) {
867                if (eventArgs.kind === Windows.ApplicationModel.Activation.ActivationKind.pickSaveFileContinuation) {
868                    var file = eventArgs.file;
869                    saveFile(file);
870                    webUIApp.removeEventListener("activated", fileSaveHandler);
871                }
872            };
873            webUIApp.addEventListener("activated", fileSaveHandler);
874            savePicker.pickSaveFileAndContinue();
875        } else {
876            savePicker.pickSaveFileAsync()
877                .done(saveFile, errorCallback);
878        }
879    }
880}
881
882require("cordova/exec/proxy").add("Camera",module.exports);