UNPKG

cordova-plugin-media-capture

Version:
618 lines (573 loc) 30.1 kB
/* * * 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 */ var MediaFile = require('cordova-plugin-media-capture.MediaFile'); var CaptureError = require('cordova-plugin-media-capture.CaptureError'); var CaptureAudioOptions = require('cordova-plugin-media-capture.CaptureAudioOptions'); var CaptureVideoOptions = require('cordova-plugin-media-capture.CaptureVideoOptions'); var MediaFileData = require('cordova-plugin-media-capture.MediaFileData'); /* * Class that combines all logic for capturing picture and video on WP8.1 */ function MediaCaptureProxy () { var previewContainer; var capturePreview = null; var captureCancelButton = null; // eslint-disable-line no-unused-vars var captureSettings = null; var captureStarted = false; var capturedPictureFile; var capturedVideoFile; var capture = null; var CaptureNS = Windows.Media.Capture; /** * Helper function that toggles visibility of DOM elements with provided ids * @param {String} variable number of elements' ids which visibility needs to be toggled */ function toggleElements () { // convert arguments to array var args = Array.prototype.slice.call(arguments); args.forEach(function (buttonId) { var buttonEl = document.getElementById(buttonId); if (buttonEl) { var curDisplayStyle = buttonEl.style.display; buttonEl.style.display = curDisplayStyle === 'none' ? 'block' : 'none'; } }); } /** * Creates basic camera UI with preview 'video' element and 'Cancel' button * Capture starts, when you clicking on preview. */ function createCameraUI () { var buttonStyle = 'margin: 7px; border: 2.5px solid white; width: 45%; height: 35px; color: white; background-color: black;'; previewContainer = document.createElement('div'); previewContainer.style.cssText = 'background-position: 50% 50%; background-repeat: no-repeat; background-size: contain; background-color: black; left: 0px; top: 0px; width: 100%; height: 100%; position: fixed; z-index: 9999'; previewContainer.innerHTML = '<video id="capturePreview" style="width: 100%; height: 100%"></video>' + '<div id="previewButtons" style="width: 100%; bottom: 0px; display: flex; position: absolute; justify-content: space-around; background-color: black;">' + '<button id="takePicture" style="' + buttonStyle + '">Capture</button>' + '<button id="cancelCapture" style="' + buttonStyle + '">Cancel</button>' + '<button id="selectPicture" style="display: none; ' + buttonStyle + '">Accept</button>' + '<button id="retakePicture" style="display: none; ' + buttonStyle + '">Retake</button>' + '</div>'; document.body.appendChild(previewContainer); // Create fullscreen preview capturePreview = document.getElementById('capturePreview'); // Create cancel button captureCancelButton = document.getElementById('cancelCapture'); capture = new CaptureNS.MediaCapture(); captureSettings = new CaptureNS.MediaCaptureInitializationSettings(); captureSettings.streamingCaptureMode = CaptureNS.StreamingCaptureMode.audioAndVideo; } /** * Starts camera preview and binds provided callbacks to controls * @param {function} takeCallback Callback for Take button * @param {function} errorCallback Callback for Cancel button + default error callback * @param {function} selectCallback Callback for Select button * @param {function} retakeCallback Callback for Retake button */ function startCameraPreview (takeCallback, errorCallback, selectCallback, retakeCallback) { // try to select appropriate device for capture // rear camera is preferred option var expectedPanel = Windows.Devices.Enumeration.Panel.back; Windows.Devices.Enumeration.DeviceInformation.findAllAsync(Windows.Devices.Enumeration.DeviceClass.videoCapture).done(function ( devices ) { if (devices.length > 0) { devices.forEach(function (currDev) { if (currDev.enclosureLocation && currDev.enclosureLocation.panel && currDev.enclosureLocation.panel === expectedPanel) { captureSettings.videoDeviceId = currDev.id; } }); capture.initializeAsync(captureSettings).done( function () { // This is necessary since WP8.1 MediaCapture outputs video stream rotated 90 degrees CCW // TODO: This can be not consistent across devices, need additional testing on various devices // msdn.microsoft.com/en-us/library/windows/apps/hh452807.aspx capture.setPreviewRotation(Windows.Media.Capture.VideoRotation.clockwise90Degrees); capturePreview.msZoom = true; capturePreview.src = URL.createObjectURL(capture); // eslint-disable-line no-undef capturePreview.play(); previewContainer.style.display = 'block'; // Bind events to controls capturePreview.onclick = takeCallback; document.getElementById('takePicture').onclick = takeCallback; document.getElementById('cancelCapture').onclick = function () { errorCallback(CaptureError.CAPTURE_NO_MEDIA_FILES); }; document.getElementById('selectPicture').onclick = selectCallback; document.getElementById('retakePicture').onclick = retakeCallback; }, function (err) { destroyCameraPreview(); errorCallback(CaptureError.CAPTURE_INTERNAL_ERR, err); } ); } else { // no appropriate devices found destroyCameraPreview(); errorCallback(CaptureError.CAPTURE_INTERNAL_ERR); } }); } /** * Destroys camera preview, removes all elements created */ function destroyCameraPreview () { capturePreview.pause(); capturePreview.src = null; if (previewContainer) { document.body.removeChild(previewContainer); } if (capture) { capture.stopRecordAsync(); capture = null; } } return { /** * Initiate video capture using MediaCapture class * @param {function} successCallback Called, when user clicked on preview, with captured file object * @param {function} errorCallback Called on any error */ captureVideo: function (successCallback, errorCallback) { try { createCameraUI(); startCameraPreview(function () { // This callback called twice: whem video capture started and when it ended // so we need to check capture status if (!captureStarted) { // remove cancel button and rename 'Take' button to 'Stop' toggleElements('cancelCapture'); document.getElementById('takePicture').text = 'Stop'; var encodingProperties = Windows.Media.MediaProperties.MediaEncodingProfile.createMp4( Windows.Media.MediaProperties.VideoEncodingQuality.auto ); var generateUniqueCollisionOption = Windows.Storage.CreationCollisionOption.generateUniqueName; var localFolder = Windows.Storage.ApplicationData.current.localFolder; localFolder.createFileAsync('cameraCaptureVideo.mp4', generateUniqueCollisionOption).done( function (capturedFile) { capture.startRecordToStorageFileAsync(encodingProperties, capturedFile).done( function () { capturedVideoFile = capturedFile; captureStarted = true; }, function (err) { destroyCameraPreview(); errorCallback(CaptureError.CAPTURE_INTERNAL_ERR, err); } ); }, function (err) { destroyCameraPreview(); errorCallback(CaptureError.CAPTURE_INTERNAL_ERR, err); } ); } else { capture.stopRecordAsync().done(function () { destroyCameraPreview(); successCallback(capturedVideoFile); }); } }, errorCallback); } catch (ex) { destroyCameraPreview(); errorCallback(CaptureError.CAPTURE_INTERNAL_ERR, ex); } }, /** * Initiate image capture using MediaCapture class * @param {function} successCallback Called, when user clicked on preview, with captured file object * @param {function} errorCallback Called on any error */ capturePhoto: function (successCallback, errorCallback) { try { createCameraUI(); startCameraPreview( // Callback for Take button - captures intermediate image file. function () { var encodingProperties = Windows.Media.MediaProperties.ImageEncodingProperties.createJpeg(); var overwriteCollisionOption = Windows.Storage.CreationCollisionOption.replaceExisting; var tempFolder = Windows.Storage.ApplicationData.current.temporaryFolder; tempFolder.createFileAsync('cameraCaptureImage.jpg', overwriteCollisionOption).done( function (capturedFile) { capture.capturePhotoToStorageFileAsync(encodingProperties, capturedFile).done( function () { // store intermediate result in object's global variable capturedPictureFile = capturedFile; // show pre-captured image and toggle visibility of all buttons previewContainer.style.backgroundImage = 'url("' + 'ms-appdata:///temp/' + capturedFile.name + '")'; toggleElements('capturePreview', 'takePicture', 'cancelCapture', 'selectPicture', 'retakePicture'); }, function (err) { destroyCameraPreview(); errorCallback(CaptureError.CAPTURE_INTERNAL_ERR, err); } ); }, function (err) { destroyCameraPreview(); errorCallback(CaptureError.CAPTURE_INTERNAL_ERR, err); } ); }, // error + cancel callback function (err) { destroyCameraPreview(); errorCallback(err); }, // Callback for Select button - copies intermediate file into persistent application's storage function () { var generateUniqueCollisionOption = Windows.Storage.CreationCollisionOption.generateUniqueName; var localFolder = Windows.Storage.ApplicationData.current.localFolder; capturedPictureFile.copyAsync(localFolder, capturedPictureFile.name, generateUniqueCollisionOption).done( function (copiedFile) { destroyCameraPreview(); successCallback(copiedFile); }, function (err) { destroyCameraPreview(); errorCallback(err); } ); }, // Callback for retake button - just toggles visibility of necessary elements function () { toggleElements('capturePreview', 'takePicture', 'cancelCapture', 'selectPicture', 'retakePicture'); } ); } catch (ex) { destroyCameraPreview(); errorCallback(CaptureError.CAPTURE_INTERNAL_ERR, ex); } } }; } module.exports = { captureAudio: function (successCallback, errorCallback, args) { var options = args[0]; var audioOptions = new CaptureAudioOptions(); if (typeof options.duration === 'undefined') { audioOptions.duration = 3600; // Arbitrary amount, need to change later } else if (options.duration > 0) { audioOptions.duration = options.duration; } else { errorCallback(new CaptureError(CaptureError.CAPTURE_INVALID_ARGUMENT)); return; } // Some shortcuts for long namespaces var CaptureNS = Windows.Media.Capture; var MediaPropsNS = Windows.Media.MediaProperties; var localAppData = Windows.Storage.ApplicationData.current.localFolder; var generateUniqueName = Windows.Storage.NameCollisionOption.generateUniqueName; var mediaCapture = new CaptureNS.MediaCapture(); var mediaCaptureSettings = new CaptureNS.MediaCaptureInitializationSettings(); var mp3EncodingProfile = new MediaPropsNS.MediaEncodingProfile.createMp3(MediaPropsNS.AudioEncodingQuality.auto); // eslint-disable-line new-cap var m4aEncodingProfile = new MediaPropsNS.MediaEncodingProfile.createM4a(MediaPropsNS.AudioEncodingQuality.auto); // eslint-disable-line new-cap mediaCaptureSettings.streamingCaptureMode = CaptureNS.StreamingCaptureMode.audio; var capturedFile; var stopRecordTimeout; var stopRecord = function () { mediaCapture.stopRecordAsync().then( function () { capturedFile.getBasicPropertiesAsync().then( function (basicProperties) { var result = new MediaFile( capturedFile.name, 'ms-appdata:///local/' + capturedFile.name, capturedFile.contentType, basicProperties.dateModified, basicProperties.size ); result.fullPath = capturedFile.path; successCallback([result]); }, function () { errorCallback(new CaptureError(CaptureError.CAPTURE_NO_MEDIA_FILES)); } ); }, function () { errorCallback(new CaptureError(CaptureError.CAPTURE_NO_MEDIA_FILES)); } ); }; mediaCapture.initializeAsync(mediaCaptureSettings).done(function () { localAppData.createFileAsync('captureAudio.mp3', generateUniqueName).then( function (storageFile) { capturedFile = storageFile; mediaCapture.startRecordToStorageFileAsync(mp3EncodingProfile, capturedFile).then( function () { stopRecordTimeout = setTimeout(stopRecord, audioOptions.duration * 1000); }, function (err) { // -1072868846 is the error code for "No suitable transform was found to encode or decode the content." // so we try to use another (m4a) format if (err.number === -1072868846) { // first we clear existing timeout to prevent success callback to be called with invalid arguments // second we start same actions to try to record m4a audio clearTimeout(stopRecordTimeout); localAppData.createFileAsync('captureAudio.m4a', generateUniqueName).then(function (storageFile) { capturedFile = storageFile; mediaCapture.startRecordToStorageFileAsync(m4aEncodingProfile, capturedFile).then( function () { stopRecordTimeout = setTimeout(stopRecord, audioOptions.duration * 1000); }, function () { // if we here, we're totally failed to record either mp3 or m4a errorCallback(new CaptureError(CaptureError.CAPTURE_INTERNAL_ERR)); } ); }); } else { errorCallback(new CaptureError(CaptureError.CAPTURE_INTERNAL_ERR)); } } ); }, function () { errorCallback(new CaptureError(CaptureError.CAPTURE_NO_MEDIA_FILES)); } ); }); }, captureImage: function (successCallback, errorCallback, args) { var CaptureNS = Windows.Media.Capture; function fail (code, data) { var err = new CaptureError(code); err.message = data; errorCallback(err); } // Check if necessary API available if (!CaptureNS.CameraCaptureUI) { // 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 proxy = new MediaCaptureProxy(); proxy.capturePhoto( function (photoFile) { photoFile.getBasicPropertiesAsync().done( function (basicProperties) { var result = new MediaFile( photoFile.name, 'ms-appdata:///local/' + photoFile.name, photoFile.contentType, basicProperties.dateModified, basicProperties.size ); result.fullPath = photoFile.path; successCallback([result]); }, function (err) { fail(CaptureError.CAPTURE_INTERNAL_ERR, err); } ); }, function (err) { fail(err); } ); } else { var cameraCaptureUI = new Windows.Media.Capture.CameraCaptureUI(); cameraCaptureUI.photoSettings.allowCropping = true; cameraCaptureUI.photoSettings.maxResolution = Windows.Media.Capture.CameraCaptureUIMaxPhotoResolution.highestAvailable; cameraCaptureUI.photoSettings.format = Windows.Media.Capture.CameraCaptureUIPhotoFormat.jpeg; cameraCaptureUI.captureFileAsync(Windows.Media.Capture.CameraCaptureUIMode.photo).done( function (file) { if (file) { file.moveAsync( Windows.Storage.ApplicationData.current.localFolder, 'cameraCaptureImage.jpg', Windows.Storage.NameCollisionOption.generateUniqueName ).then( function () { file.getBasicPropertiesAsync().then( function (basicProperties) { var result = new MediaFile( file.name, 'ms-appdata:///local/' + file.name, file.contentType, basicProperties.dateModified, basicProperties.size ); result.fullPath = file.path; successCallback([result]); }, function () { errorCallback(new CaptureError(CaptureError.CAPTURE_NO_MEDIA_FILES)); } ); }, function () { errorCallback(new CaptureError(CaptureError.CAPTURE_NO_MEDIA_FILES)); } ); } else { errorCallback(new CaptureError(CaptureError.CAPTURE_NO_MEDIA_FILES)); } }, function () { errorCallback(new CaptureError(CaptureError.CAPTURE_NO_MEDIA_FILES)); } ); } }, captureVideo: function (successCallback, errorCallback, args) { var options = args[0]; var CaptureNS = Windows.Media.Capture; function fail (code, data) { var err = new CaptureError(code); err.message = data; errorCallback(err); } // Check if necessary API available if (!CaptureNS.CameraCaptureUI) { // 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 proxy = new MediaCaptureProxy(); proxy.captureVideo(function (videoFile) { videoFile.getBasicPropertiesAsync().done( function (basicProperties) { var result = new MediaFile( videoFile.name, 'ms-appdata:///local/' + videoFile.name, videoFile.contentType, basicProperties.dateModified, basicProperties.size ); result.fullPath = videoFile.path; successCallback([result]); }, function (err) { fail(CaptureError.CAPTURE_INTERNAL_ERR, err); } ); }, fail); } else { var videoOptions = new CaptureVideoOptions(); if (options.duration && options.duration > 0) { videoOptions.duration = options.duration; } if (options.limit > 1) { videoOptions.limit = options.limit; } var cameraCaptureUI = new Windows.Media.Capture.CameraCaptureUI(); cameraCaptureUI.videoSettings.allowTrimming = true; cameraCaptureUI.videoSettings.format = Windows.Media.Capture.CameraCaptureUIVideoFormat.mp4; cameraCaptureUI.videoSettings.maxDurationInSeconds = videoOptions.duration; cameraCaptureUI.captureFileAsync(Windows.Media.Capture.CameraCaptureUIMode.video).then( function (file) { if (file) { file.moveAsync( Windows.Storage.ApplicationData.current.localFolder, 'cameraCaptureVideo.mp4', Windows.Storage.NameCollisionOption.generateUniqueName ).then( function () { file.getBasicPropertiesAsync().then( function (basicProperties) { var result = new MediaFile( file.name, 'ms-appdata:///local/' + file.name, file.contentType, basicProperties.dateModified, basicProperties.size ); result.fullPath = file.path; successCallback([result]); }, function () { errorCallback(new CaptureError(CaptureError.CAPTURE_NO_MEDIA_FILES)); } ); }, function () { errorCallback(new CaptureError(CaptureError.CAPTURE_NO_MEDIA_FILES)); } ); } else { errorCallback(new CaptureError(CaptureError.CAPTURE_NO_MEDIA_FILES)); } }, function () { errorCallback(new CaptureError(CaptureError.CAPTURE_NO_MEDIA_FILES)); } ); } }, getFormatData: function (successCallback, errorCallback, args) { Windows.Storage.StorageFile.getFileFromPathAsync(args[0]).then( function (storageFile) { var mediaTypeFlag = String(storageFile.contentType).split('/')[0].toLowerCase(); if (mediaTypeFlag === 'audio') { storageFile.properties.getMusicPropertiesAsync().then( function (audioProperties) { successCallback(new MediaFileData(null, audioProperties.bitrate, 0, 0, audioProperties.duration / 1000)); }, function () { errorCallback(new CaptureError(CaptureError.CAPTURE_INVALID_ARGUMENT)); } ); } else if (mediaTypeFlag === 'video') { storageFile.properties.getVideoPropertiesAsync().then( function (videoProperties) { successCallback( new MediaFileData( null, videoProperties.bitrate, videoProperties.height, videoProperties.width, videoProperties.duration / 1000 ) ); }, function () { errorCallback(new CaptureError(CaptureError.CAPTURE_INVALID_ARGUMENT)); } ); } else if (mediaTypeFlag === 'image') { storageFile.properties.getImagePropertiesAsync().then( function (imageProperties) { successCallback(new MediaFileData(null, 0, imageProperties.height, imageProperties.width, 0)); }, function () { errorCallback(new CaptureError(CaptureError.CAPTURE_INVALID_ARGUMENT)); } ); } else { errorCallback(new CaptureError(CaptureError.CAPTURE_INVALID_ARGUMENT)); } }, function () { errorCallback(new CaptureError(CaptureError.CAPTURE_INVALID_ARGUMENT)); } ); } }; require('cordova/exec/proxy').add('Capture', module.exports);