From b05b77a14a5fdab25ff7d95985a4001fa20f7d99 Mon Sep 17 00:00:00 2001 From: Gedas Gardauskas Date: Fri, 29 Mar 2019 17:03:22 +0900 Subject: [PATCH 1/3] Update browser plugin - Add ability to specify camera container, buttons and source input - Add cancel button, that can stop video stream - Refactor code --- src/browser/CameraProxy.js | 197 +++++++++++++++++++++++++------------ www/Camera.js | 50 +++++----- 2 files changed, 160 insertions(+), 87 deletions(-) diff --git a/src/browser/CameraProxy.js b/src/browser/CameraProxy.js index ff81257a6..b11e4443a 100644 --- a/src/browser/CameraProxy.js +++ b/src/browser/CameraProxy.js @@ -19,105 +19,176 @@ * */ -var HIGHEST_POSSIBLE_Z_INDEX = 2147483647; +let localMediaStream; -function takePicture (success, error, opts) { +function takePicture (successCallback, errorCallback, opts) { if (opts && opts[2] === 1) { - capture(success, error, opts); + capture(successCallback, errorCallback, opts); } else { - var input = document.createElement('input'); - input.style.position = 'relative'; - input.style.zIndex = HIGHEST_POSSIBLE_Z_INDEX; - input.className = 'cordova-camera-select'; - input.type = 'file'; - input.name = 'files[]'; + const customSourceInput = opts[15]; - input.onchange = function (inputEvent) { - var reader = new FileReader(); /* eslint no-undef : 0 */ - reader.onload = function (readerEvent) { - input.parentNode.removeChild(input); + let sourceInput = customSourceInput ? document.getElementById(customSourceInput) : createSourceInput(); - var imageData = readerEvent.target.result; + handleSourceInput(successCallback, sourceInput); - return success(imageData.substr(imageData.indexOf(',') + 1)); - }; - - reader.readAsDataURL(inputEvent.target.files[0]); - }; + if (!customSourceInput) { + document.body.appendChild(sourceInput); + } + } +} - document.body.appendChild(input); +function capture (successCallback, errorCallback, opts) { + let targetWidth = opts[3]; + let targetHeight = opts[4]; + const customCameraContainer = opts[12]; + const customCaptureButton = opts[13]; + const customCancelButton = opts[14]; + + let parent = customCameraContainer ? document.getElementById(customCameraContainer) : createCameraContainer(); + let video = createVideoStreamContainer(parent, targetWidth, targetHeight); + let captureButton = customCaptureButton ? document.getElementById(customCaptureButton) : createButton(parent, 'Capture'); + let cancelButton = customCancelButton ? document.getElementById(customCancelButton) : createButton(parent, 'Cancel'); + + // start video stream + startLocalMediaStream(errorCallback, video); + + // if custom camera container is not set by the user, + // append parent to the document.body + if (!customCameraContainer) { + document.body.appendChild(video.parentNode); } + + // handle button click events + handleCaptureButton(successCallback, errorCallback, captureButton, video, customCameraContainer); + handleCancelButton(cancelButton, video, customCameraContainer); } -function capture (success, errorCallback, opts) { - var localMediaStream; - var targetWidth = opts[3]; - var targetHeight = opts[4]; +function createCameraContainer () { + let parent = document.createElement('div'); + parent.style.position = 'relative'; + parent.style.zIndex = '2147483647'; // set highest possible z index + parent.className = 'cordova-camera-capture'; + + return parent; +} +function createVideoStreamContainer (parent, targetWidth, targetHeight) { targetWidth = targetWidth === -1 ? 320 : targetWidth; targetHeight = targetHeight === -1 ? 240 : targetHeight; - var video = document.createElement('video'); - var button = document.createElement('button'); - var parent = document.createElement('div'); - parent.style.position = 'relative'; - parent.style.zIndex = HIGHEST_POSSIBLE_Z_INDEX; - parent.className = 'cordova-camera-capture'; + let video = document.createElement('video'); + video.width = targetWidth; + video.height = targetHeight; + parent.appendChild(video); + + return video; +} + +function createButton (parent, innerText) { + let button = document.createElement('button'); + button.innerHTML = innerText; + parent.appendChild(button); - video.width = targetWidth; - video.height = targetHeight; - button.innerHTML = 'Capture!'; + return button; +} - button.onclick = function () { +function handleCaptureButton (successCallback, errorCallback, captureButton, video, customCameraContainer) { + captureButton.onclick = function () { // create a canvas and capture a frame from video stream - var canvas = document.createElement('canvas'); - canvas.width = targetWidth; - canvas.height = targetHeight; - canvas.getContext('2d').drawImage(video, 0, 0, targetWidth, targetHeight); + let canvas = document.createElement('canvas'); + canvas.width = video.width; + canvas.height = video.height; + canvas.getContext('2d').drawImage(video, 0, 0, video.width, video.height); // convert image stored in canvas to base64 encoded image - var imageData = canvas.toDataURL('image/png'); + let imageData = canvas.toDataURL('image/png'); imageData = imageData.replace('data:image/png;base64,', ''); - // stop video stream, remove video and button. - // Note that MediaStream.stop() is deprecated as of Chrome 47. - if (localMediaStream.stop) { - localMediaStream.stop(); - } else { - localMediaStream.getTracks().forEach(function (track) { - track.stop(); - }); - } - parent.parentNode.removeChild(parent); + // stop video stream + stopLocalMediaStream(video, customCameraContainer); - return success(imageData); + return successCallback(imageData); }; +} - navigator.getUserMedia = navigator.getUserMedia || - navigator.webkitGetUserMedia || - navigator.mozGetUserMedia || - navigator.msGetUserMedia; +function handleCancelButton (cancelButton, video, customCameraContainer) { + cancelButton.onclick = function () { + // stop video stream + stopLocalMediaStream(video, customCameraContainer); + }; +} - var successCallback = function (stream) { +function startLocalMediaStream (errorCallback, video) { + + const successCallback = function (stream) { localMediaStream = stream; - if ('srcObject' in video) { - video.srcObject = localMediaStream; - } else { - video.src = window.URL.createObjectURL(localMediaStream); - } + video.src = window.URL.createObjectURL(localMediaStream); video.play(); - document.body.appendChild(parent); }; + navigator.getUserMedia = navigator.getUserMedia || + navigator.webkitGetUserMedia || + navigator.mozGetUserMedia || + navigator.msGetUserMedia; + if (navigator.getUserMedia) { - navigator.getUserMedia({video: true, audio: false}, successCallback, errorCallback); + navigator.getUserMedia({video: true, audio: true}, successCallback, errorCallback); } else { - alert('Browser does not support camera :('); + alert('Your browser does not support camera.'); } } +function stopLocalMediaStream (video, customCameraContainer) { + // stop video stream, remove video and captureButton. + // note: MediaStream.stop() is deprecated as of Chrome 47. + if (localMediaStream.stop) { + localMediaStream.stop(); + } else { + localMediaStream.getTracks().forEach(function (track) { + track.stop(); + }); + } + + // remove newly created elements + removeAppendedCameraElements(video, customCameraContainer); +} + +function removeAppendedCameraElements (video, customCameraContainer) { + const parent = video.parentNode; + if (!customCameraContainer) { + parent.parentNode.removeChild(parent); + } else { + parent.removeChild(video); + } +} + +function createSourceInput () { + let input = document.createElement('input'); + input.style.position = 'relative'; + input.style.zIndex = '2147483647'; // set highest possible z index + input.className = 'cordova-camera-select'; + input.type = 'file'; + input.name = 'files[]'; + + return input; +} + +function handleSourceInput (successCallback, sourceInput) { + sourceInput.onchange = function (inputEvent) { + let reader = new FileReader(); /* eslint no-undef : 0 */ + reader.onload = function (readerEvent) { + sourceInput.parentNode.removeChild(sourceInput); + + const imageData = readerEvent.target.result; + + return successCallback(imageData.substr(imageData.indexOf(',') + 1)); + }; + reader.readAsDataURL(inputEvent.target.files[0]); + }; +} + module.exports = { takePicture: takePicture, cleanup: function () {} diff --git a/www/Camera.js b/www/Camera.js index 0eade0fe3..93e9d1335 100644 --- a/www/Camera.js +++ b/www/Camera.js @@ -19,11 +19,9 @@ * */ -var argscheck = require('cordova/argscheck'); -var exec = require('cordova/exec'); -var Camera = require('./Camera'); -// XXX: commented out -// CameraPopoverHandle = require('./CameraPopoverHandle'); +const argscheck = require('cordova/argscheck'); +const exec = require('cordova/exec'); +const Camera = require('./Camera'); /** * @namespace navigator @@ -32,10 +30,10 @@ var Camera = require('./Camera'); /** * @exports camera */ -var cameraExport = {}; +const cameraExport = {}; // Tack on the Camera Constants to the base camera plugin. -for (var key in Camera) { +for (let key in Camera) { cameraExport[key] = Camera[key]; } @@ -134,27 +132,31 @@ for (var key in Camera) { cameraExport.getPicture = function (successCallback, errorCallback, options) { argscheck.checkArgs('fFO', 'Camera.getPicture', arguments); options = options || {}; - var getValue = argscheck.getValue; + const getValue = argscheck.getValue; - var quality = getValue(options.quality, 50); - var destinationType = getValue(options.destinationType, Camera.DestinationType.FILE_URI); - var sourceType = getValue(options.sourceType, Camera.PictureSourceType.CAMERA); - var targetWidth = getValue(options.targetWidth, -1); - var targetHeight = getValue(options.targetHeight, -1); - var encodingType = getValue(options.encodingType, Camera.EncodingType.JPEG); - var mediaType = getValue(options.mediaType, Camera.MediaType.PICTURE); - var allowEdit = !!options.allowEdit; - var correctOrientation = !!options.correctOrientation; - var saveToPhotoAlbum = !!options.saveToPhotoAlbum; - var popoverOptions = getValue(options.popoverOptions, null); - var cameraDirection = getValue(options.cameraDirection, Camera.Direction.BACK); + const quality = getValue(options.quality, 50); + const destinationType = getValue(options.destinationType, Camera.DestinationType.FILE_URI); + const sourceType = getValue(options.sourceType, Camera.PictureSourceType.CAMERA); + const targetWidth = getValue(options.targetWidth, -1); + const targetHeight = getValue(options.targetHeight, -1); + const encodingType = getValue(options.encodingType, Camera.EncodingType.JPEG); + const mediaType = getValue(options.mediaType, Camera.MediaType.PICTURE); + const allowEdit = !!options.allowEdit; + const correctOrientation = !!options.correctOrientation; + const saveToPhotoAlbum = !!options.saveToPhotoAlbum; + const popoverOptions = getValue(options.popoverOptions, null); + const cameraDirection = getValue(options.cameraDirection, Camera.Direction.BACK); - var args = [quality, destinationType, sourceType, targetWidth, targetHeight, encodingType, - mediaType, allowEdit, correctOrientation, saveToPhotoAlbum, popoverOptions, cameraDirection]; + const customCameraContainer = getValue(options.customCameraContainer, null); + const customCaptureButton = getValue(options.customCaptureButton, null); + const customCancelButton = getValue(options.customCancelButton, null); + const customSourceInput = getValue(options.customSourceInput, null); + + const args = [quality, destinationType, sourceType, targetWidth, targetHeight, encodingType, + mediaType, allowEdit, correctOrientation, saveToPhotoAlbum, popoverOptions, cameraDirection, + customCameraContainer, customCaptureButton, customCancelButton, customSourceInput]; exec(successCallback, errorCallback, 'Camera', 'takePicture', args); - // XXX: commented out - // return new CameraPopoverHandle(); }; /** From 5a8765b2df092b4e51aebd022a14074c4b035c79 Mon Sep 17 00:00:00 2001 From: Gedas Gardauskas Date: Fri, 29 Mar 2019 17:03:36 +0900 Subject: [PATCH 2/3] Update README.md --- README.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/README.md b/README.md index f3d04d56e..73e3187cf 100644 --- a/README.md +++ b/README.md @@ -266,6 +266,10 @@ Optional parameters to customize the camera settings. | saveToPhotoAlbum | Boolean | | Save the image to the photo album on the device after capture. | | popoverOptions | [CameraPopoverOptions](#module_CameraPopoverOptions) | | iOS-only options that specify popover location in iPad. | | cameraDirection | [Direction](#module_Camera.Direction) | BACK | Choose the camera to use (front- or back-facing). | +| customCameraContainer | string | | Browser-only option, specify the id of custom camera's container element. | +| customCaptureButton | string | | Browser-only option, specify the id of custom camera's capture button. | +| customCancelButton | string | | Browser-only option, specify the id of custom camera's cancel button. | +| customSourceInput | string | | Browser-only option, specify the id of custom source input element, when using diffrerent mode than the default `sourceType`. | --- From 9b030a1d92cf70dd4f51bd1a3ab3c6c7ca0a8360 Mon Sep 17 00:00:00 2001 From: Gedas Gardauskas Date: Fri, 29 Mar 2019 19:12:41 +0900 Subject: [PATCH 3/3] Update appended elements cleanup --- src/browser/CameraProxy.js | 35 ++++++++++++++++++++++++----------- 1 file changed, 24 insertions(+), 11 deletions(-) diff --git a/src/browser/CameraProxy.js b/src/browser/CameraProxy.js index b11e4443a..03489b3d1 100644 --- a/src/browser/CameraProxy.js +++ b/src/browser/CameraProxy.js @@ -44,11 +44,12 @@ function capture (successCallback, errorCallback, opts) { const customCaptureButton = opts[13]; const customCancelButton = opts[14]; + const customElements = {customCameraContainer, customCaptureButton, customCancelButton}; + let parent = customCameraContainer ? document.getElementById(customCameraContainer) : createCameraContainer(); let video = createVideoStreamContainer(parent, targetWidth, targetHeight); let captureButton = customCaptureButton ? document.getElementById(customCaptureButton) : createButton(parent, 'Capture'); let cancelButton = customCancelButton ? document.getElementById(customCancelButton) : createButton(parent, 'Cancel'); - // start video stream startLocalMediaStream(errorCallback, video); @@ -59,8 +60,8 @@ function capture (successCallback, errorCallback, opts) { } // handle button click events - handleCaptureButton(successCallback, errorCallback, captureButton, video, customCameraContainer); - handleCancelButton(cancelButton, video, customCameraContainer); + handleCaptureButton(successCallback, errorCallback, captureButton, video, customElements); + handleCancelButton(cancelButton, video, customElements); } function createCameraContainer () { @@ -94,7 +95,7 @@ function createButton (parent, innerText) { return button; } -function handleCaptureButton (successCallback, errorCallback, captureButton, video, customCameraContainer) { +function handleCaptureButton (successCallback, errorCallback, captureButton, video, customElements) { captureButton.onclick = function () { // create a canvas and capture a frame from video stream let canvas = document.createElement('canvas'); @@ -107,16 +108,16 @@ function handleCaptureButton (successCallback, errorCallback, captureButton, vid imageData = imageData.replace('data:image/png;base64,', ''); // stop video stream - stopLocalMediaStream(video, customCameraContainer); + stopLocalMediaStream(video, customElements); return successCallback(imageData); }; } -function handleCancelButton (cancelButton, video, customCameraContainer) { +function handleCancelButton (cancelButton, video, customElements) { cancelButton.onclick = function () { // stop video stream - stopLocalMediaStream(video, customCameraContainer); + stopLocalMediaStream(video, customElements); }; } @@ -140,7 +141,7 @@ function startLocalMediaStream (errorCallback, video) { } } -function stopLocalMediaStream (video, customCameraContainer) { +function stopLocalMediaStream (video, customElements) { // stop video stream, remove video and captureButton. // note: MediaStream.stop() is deprecated as of Chrome 47. if (localMediaStream.stop) { @@ -152,13 +153,25 @@ function stopLocalMediaStream (video, customCameraContainer) { } // remove newly created elements - removeAppendedCameraElements(video, customCameraContainer); + removeAppendedCameraElements(video, customElements); } -function removeAppendedCameraElements (video, customCameraContainer) { +function removeAppendedCameraElements (video, customElements) { + const parent = video.parentNode; - if (!customCameraContainer) { + + if (!customElements.customCameraContainer) { parent.parentNode.removeChild(parent); + } else if (!customElements.customCaptureButton && !customElements.customCancelButton) { + while (parent.hasChildNodes()) { + parent.removeChild(parent.lastChild); + } + } else if (parent.hasChildNodes() && !customElements.customCaptureButton) { + parent.removeChild(video); + parent.removeChild(parent.lastChild); + } else if (parent.hasChildNodes() && !customElements.customCancelButton) { + parent.removeChild(video); + parent.removeChild(parent.lastChild); } else { parent.removeChild(video); }