in src/windows/CameraProxy.js [293:681]
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
var destinationType = args[1];
var targetWidth = args[3];
var targetHeight = args[4];
var encodingType = args[5];
var saveToPhotoAlbum = args[9];
var cameraDirection = args[11];
var capturePreview = null;
var cameraCaptureButton = null;
var cameraCancelButton = null;
var capture = null;
var captureSettings = null;
var CaptureNS = Windows.Media.Capture;
var sensor = null;
function createCameraUI () {
// create style for take and cancel buttons
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;';
// 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
var DeviceEnum = Windows.Devices.Enumeration;
var 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
var VideoDeviceController = capture.videoDeviceController;
var 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');
}
var preset = Windows.Media.Devices.FocusPreset.autoNormal;
var 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
var aspectRatios = getAspectRatios(capture);
// Couldn't find a good ratio
if (aspectRatios.length === 0) {
destroyCameraPreview();
errorCallback('There\'s not a good aspect ratio available');
return;
}
// 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 () {
var encodingProperties;
var fileName;
var 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) {
var photoStream = new Windows.Storage.Streams.InMemoryRandomAccessStream();
var finalStream = new Windows.Storage.Streams.InMemoryRandomAccessStream();
capture.capturePhotoToStreamAsync(encodingProperties, photoStream)
.then(function () {
return Windows.Graphics.Imaging.BitmapDecoder.createAsync(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: destinationType,
targetHeight: targetHeight,
targetWidth: targetWidth,
encodingType: encodingType,
saveToPhotoAlbum: saveToPhotoAlbum
}, successCallback, errorCallback);
}, function (err) {
destroyCameraPreview();
errorCallback(err);
});
}
function getAspectRatios (capture) {
var videoDeviceController = capture.videoDeviceController;
var photoAspectRatios = videoDeviceController.getAvailableMediaStreamProperties(CapMSType.photo).map(function (element) {
return (element.width / element.height).toFixed(1);
}).filter(function (element, index, array) { return (index === array.indexOf(element)); });
var videoAspectRatios = videoDeviceController.getAvailableMediaStreamProperties(CapMSType.videoRecord).map(function (element) {
return (element.width / element.height).toFixed(1);
}).filter(function (element, index, array) { return (index === array.indexOf(element)); });
var videoPreviewAspectRatios = videoDeviceController.getAvailableMediaStreamProperties(CapMSType.videoPreview).map(function (element) {
return (element.width / element.height).toFixed(1);
}).filter(function (element, index, array) { return (index === array.indexOf(element)); });
var allAspectRatios = [].concat(photoAspectRatios, videoAspectRatios, videoPreviewAspectRatios);
var aspectObj = allAspectRatios.reduce(function (map, item) {
if (!map[item]) {
map[item] = 0;
}
map[item]++;
return map;
}, {});
return Object.keys(aspectObj).filter(function (k) {
return aspectObj[k] === 3;
});
}
function setAspectRatio (capture, aspect) {
// Max photo resolution with desired aspect ratio
var videoDeviceController = capture.videoDeviceController;
var photoResolution = videoDeviceController.getAvailableMediaStreamProperties(CapMSType.photo)
.filter(function (elem) {
return ((elem.width / elem.height).toFixed(1) === aspect);
})
.reduce(function (prop1, prop2) {
return (prop1.width * prop1.height) > (prop2.width * prop2.height) ? prop1 : prop2;
});
// Max video resolution with desired aspect ratio
var videoRecordResolution = videoDeviceController.getAvailableMediaStreamProperties(CapMSType.videoRecord)
.filter(function (elem) {
return ((elem.width / elem.height).toFixed(1) === aspect);
})
.reduce(function (prop1, prop2) {
return (prop1.width * prop1.height) > (prop2.width * prop2.height) ? prop1 : prop2;
});
// Max video preview resolution with desired aspect ratio
var videoPreviewResolution = videoDeviceController.getAvailableMediaStreamProperties(CapMSType.videoPreview)
.filter(function (elem) {
return ((elem.width / elem.height).toFixed(1) === aspect);
})
.reduce(function (prop1, prop2) {
return (prop1.width * prop1.height) > (prop2.width * prop2.height) ? prop1 : prop2;
});
return videoDeviceController.setMediaStreamPropertiesAsync(CapMSType.photo, photoResolution)
.then(function () {
return videoDeviceController.setMediaStreamPropertiesAsync(CapMSType.videoPreview, videoPreviewResolution);
})
.then(function () {
return videoDeviceController.setMediaStreamPropertiesAsync(CapMSType.videoRecord, videoRecordResolution);
});
}
/**
* When 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);
}
}