-
Notifications
You must be signed in to change notification settings - Fork 1.6k
Update browser plugin #435
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: master
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -266,6 +266,10 @@ Optional parameters to customize the camera settings. | |
| | saveToPhotoAlbum | <code>Boolean</code> | | Save the image to the photo album on the device after capture. | | ||
| | popoverOptions | <code>[CameraPopoverOptions](#module_CameraPopoverOptions)</code> | | iOS-only options that specify popover location in iPad. | | ||
| | cameraDirection | <code>[Direction](#module_Camera.Direction)</code> | <code>BACK</code> | Choose the camera to use (front- or back-facing). | | ||
| | customCameraContainer | <code>string</code> | | Browser-only option, specify the id of custom camera's container element. | | ||
| | customCaptureButton | <code>string</code> | | Browser-only option, specify the id of custom camera's capture button. | | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Same as above [1]. |
||
| | customCancelButton | <code>string</code> | | Browser-only option, specify the id of custom camera's cancel button. | | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Same as above [1]. |
||
| | customSourceInput | <code>string</code> | | Browser-only option, specify the id of custom source input element, when using diffrerent mode than the default `sourceType`. | | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Same as above [1]. |
||
|
|
||
| --- | ||
|
|
||
|
|
||
| Original file line number | Diff line number | Diff line change | ||||
|---|---|---|---|---|---|---|
|
|
@@ -19,103 +19,187 @@ | |||||
| * | ||||||
| */ | ||||||
|
|
||||||
| 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]; | ||||||
|
|
||||||
| const customElements = {customCameraContainer, customCaptureButton, customCancelButton}; | ||||||
|
|
||||||
| let parent = customCameraContainer ? document.getElementById(customCameraContainer) : createCameraContainer(); | ||||||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
|
||||||
| let video = createVideoStreamContainer(parent, targetWidth, targetHeight); | ||||||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
|
||||||
| let captureButton = customCaptureButton ? document.getElementById(customCaptureButton) : createButton(parent, 'Capture'); | ||||||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
|
||||||
| let cancelButton = customCancelButton ? document.getElementById(customCancelButton) : createButton(parent, 'Cancel'); | ||||||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
|
||||||
| // 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, customElements); | ||||||
| handleCancelButton(cancelButton, video, customElements); | ||||||
| } | ||||||
|
|
||||||
| function capture (success, errorCallback, opts) { | ||||||
| var localMediaStream; | ||||||
| var targetWidth = opts[3]; | ||||||
| var targetHeight = opts[4]; | ||||||
| function createCameraContainer () { | ||||||
| let parent = document.createElement('div'); | ||||||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
|
||||||
| 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'); | ||||||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
|
||||||
| video.width = targetWidth; | ||||||
| video.height = targetHeight; | ||||||
|
|
||||||
| parent.appendChild(video); | ||||||
|
|
||||||
| return video; | ||||||
| } | ||||||
|
|
||||||
| function createButton (parent, innerText) { | ||||||
| let button = document.createElement('button'); | ||||||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
|
||||||
| 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, customElements) { | ||||||
| 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'); | ||||||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
|
||||||
| 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, customElements); | ||||||
|
|
||||||
| return success(imageData); | ||||||
| return successCallback(imageData); | ||||||
| }; | ||||||
| } | ||||||
|
|
||||||
| navigator.getUserMedia = navigator.getUserMedia || | ||||||
| navigator.webkitGetUserMedia || | ||||||
| navigator.mozGetUserMedia || | ||||||
| navigator.msGetUserMedia; | ||||||
| function handleCancelButton (cancelButton, video, customElements) { | ||||||
| cancelButton.onclick = function () { | ||||||
| // stop video stream | ||||||
| stopLocalMediaStream(video, customElements); | ||||||
| }; | ||||||
| } | ||||||
|
|
||||||
| function startLocalMediaStream (errorCallback, video) { | ||||||
|
|
||||||
| var successCallback = function (stream) { | ||||||
| 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('Your browser does not support camera.'); | ||||||
| } | ||||||
| } | ||||||
|
|
||||||
| function stopLocalMediaStream (video, customElements) { | ||||||
| // stop video stream, remove video and captureButton. | ||||||
| // note: MediaStream.stop() is deprecated as of Chrome 47. | ||||||
| if (localMediaStream.stop) { | ||||||
| localMediaStream.stop(); | ||||||
| } else { | ||||||
| alert('Browser does not support camera :('); | ||||||
| localMediaStream.getTracks().forEach(function (track) { | ||||||
| track.stop(); | ||||||
| }); | ||||||
| } | ||||||
|
|
||||||
| // remove newly created elements | ||||||
| removeAppendedCameraElements(video, customElements); | ||||||
| } | ||||||
|
|
||||||
| function removeAppendedCameraElements (video, customElements) { | ||||||
|
|
||||||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
|
||||||
| const parent = video.parentNode; | ||||||
|
|
||||||
| 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); | ||||||
| } | ||||||
| } | ||||||
|
|
||||||
| function createSourceInput () { | ||||||
| let input = document.createElement('input'); | ||||||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
|
||||||
| 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 */ | ||||||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
|
||||||
| 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 = { | ||||||
|
|
||||||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
[1] IMO, I think it would be better if these new options accept either a
querystring orDOMElement.Querystring would give more flexibility in how the element is discovered vs an ID only. In this case, we would need to use thedocument.querySelectormethod instead of thegetElementById.DOMElementcould also be a viable option.There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
customCameraContainersounds like is something you need a specificDOMElement, so allowing flexibility ofquerySelectorI think promotes bad coding practice.If you need a specific element, the element should be tagged by an ID as to guarantee that you will receive the expected
DOMElement. UsingquerySelectormakes code brittle by picking the first occurrence of the selected term, which may not be the node you want.The YouTube JS API[1] for example does something similar where it needs to insert HTML in a specific node of your choice. Their API signature accepts either an id, or the
HTMLElementitself.So I would prefer to keep
customCameraContaineraccepting anidrather than a query string. For flexibility, it could also optionally accept a DOM node and it would be up to the developer to find that reference as they see fit (which they could usequerySelectorif they wanted to, or it could be a fresh node that isn't even in the DOM tree yet (viadocument.createElement), etc.[1] https://developers.google.com/youtube/iframe_api_reference#Loading_a_Video_Player
Uh oh!
There was an error while loading. Please reload this page.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I believe this PR is no longer valid and can be closed.
There was a discussion after the PR creation and a decision that users should maybe use WebRTC camera functionality instead of this. After that discussion, the progression of this PR halted.
If memory serves, iOS has limitation/problems with WebRTC camera functionality so the plugin will still be needed for iOS. It can be conditionally wrapped to decided when to use WebRTC over Plugin.
The PR author created this blog post: "Cross-Platform development with Cordova and Electron" which uses the
WebRTC camera functionalityfor the sample application.There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Must be this limitation:
https://caniuse.com/#search=getUserMedia