diff --git a/src/components/camera-modal/camera-modal.css b/src/components/camera-modal/camera-modal.css index c86e7f0ca2964f58771003bf6fa87f93e2fb2103..886fc756fbb5120b254d5fb0e9511e11e1609745 100644 --- a/src/components/camera-modal/camera-modal.css +++ b/src/components/camera-modal/camera-modal.css @@ -1,6 +1,8 @@ @import "../../css/colors.css"; @import "../../css/units.css"; +$main-button-size: 2.75rem; + .modal-content { width: 600px; } @@ -30,11 +32,12 @@ width: 480px; height: 360px; position: relative; + border-radius: 1px solid $ui-black-transparent; } .canvas { width: 480px; - height: 380px; + height: 360px; } .help-text { @@ -43,6 +46,7 @@ color: rgb(167, 170, 181); font-size: 0.95rem; font-weight: 500; + text-align: center; } .capture-text { @@ -55,26 +59,65 @@ margin-top: 15px; } -.main-button-row button { +/* Action Menu */ +.main-button { + display: flex; + flex-direction: column; + align-items: center; + justify-content: center; + cursor: pointer; + font-family: "Helvetica Neue", Helvetica, Arial, sans-serif; + background: $motion-primary; + outline: none; + border: none; + transition: background-color 0.2s; + + border-radius: 100%; + width: $main-button-size; + height: $main-button-size; + box-shadow: 0 0 0 4px $motion-transparent; + z-index: 20; /* TODO reorder layout to prevent z-index need */ + transition: transform, box-shadow 0.5s; +} + +.main-button:hover { + background: $pen-primary; + + /* transform: scale(1.1); */ + box-shadow: 0 0 0 6px $motion-transparent; +} + +.main-icon { + width: calc($main-button-size - 1rem); + height: calc($main-button-size - 1rem); +} + + +/* Action Menu */ + + + +/* .main-button-row button { padding: 0.5rem 0.75rem; border-radius: 0.25rem; background: transparent; border: none; -} +} */ -.main-button-row button:disabled { +/* .main-button-row button:disabled { opacity: 0.25; } .main-button-row button:active, .main-button-row button:focus { outline: none; -} +} */ .button-row { font-weight: bolder; text-align: right; display: flex; justify-content: space-between; + margin-top: 20px; } .button-row button { @@ -97,9 +140,9 @@ margin-left: 0.5rem; } -.main-button { +/* .main-button { text-align: center; -} +} */ .capture-button { overflow: visible; diff --git a/src/components/camera-modal/camera-modal.jsx b/src/components/camera-modal/camera-modal.jsx index c39070c6a97db62bda622063ed995cf316e3fc03..d996dcbd54b667f3f9716a6641880f531f127a37 100644 --- a/src/components/camera-modal/camera-modal.jsx +++ b/src/components/camera-modal/camera-modal.jsx @@ -2,8 +2,10 @@ import PropTypes from 'prop-types'; import React from 'react'; import Box from '../box/box.jsx'; import Modal from '../modal/modal.jsx'; +import classNames from 'classnames'; import styles from './camera-modal.css'; import backIcon from './icon--back.svg'; +import cameraIcon from '../action-menu/icon--camera.svg'; const CameraModal = props => ( <Modal @@ -45,30 +47,18 @@ const CameraModal = props => ( className={styles.mainButton} onClick={props.onCapture} > - <svg - className={styles.captureButton} - height="52" - width="52" - > - <circle - className={styles.captureButtonCircle} - cx="26" - cy="26" - r="25" - /> - <circle - className={styles.captureButtonCircleOutline} - cx="26" - cy="26" - r="27" - /> - </svg> - <div className={styles.helpText}> - <span className={styles.captureText}> - {'Take Photo'} - </span> - </div> + <img + className={styles.mainIcon} + draggable={false} + src={cameraIcon} + /> </button> + <div className={styles.helpText}> + <span className={styles.captureText}> + {'Take Photo'} + </span> + </div> + </Box> } </Box> @@ -77,7 +67,7 @@ const CameraModal = props => ( CameraModal.propTypes = { canvasRef: PropTypes.func.isRequired, - capture: PropTypes.instanceOf(ImageData), + capture: PropTypes.string, onBack: PropTypes.func.isRequired, onCancel: PropTypes.func.isRequired, onCapture: PropTypes.func.isRequired, diff --git a/src/containers/camera-modal.jsx b/src/containers/camera-modal.jsx index 1b7a5de9e36ca3ebbecf1f8c8cc6cb8226bf2de7..e5cf3fe5954355625186c868857bc27b6e2f1d82 100644 --- a/src/containers/camera-modal.jsx +++ b/src/containers/camera-modal.jsx @@ -14,10 +14,10 @@ class CameraModal extends React.Component { constructor (props) { super(props); bindAll(this, [ + 'handleBack', + 'handleCancel', 'handleCapture', - // 'setVideoInput', 'handleSubmit', - 'handleCancel', 'setCanvas' // 'enableVideo' ]); @@ -35,6 +35,10 @@ class CameraModal extends React.Component { this.videoDevice.disableVideo(); // this.video = null; } + handleBack () { + this.setState({capture: null}); + this.videoDevice.clearSnapshot(); + } handleCapture () { const capture = this.videoDevice.takeSnapshot(); this.setState({capture: capture}); @@ -58,10 +62,10 @@ class CameraModal extends React.Component { return ( <CameraModalComponent // vm={this.props.vm} - // onBack={this.handleBack} canvasRef={this.setCanvas} capture={this.state.capture} // videoRef={this.setVideoInput} + onBack={this.handleBack} onCancel={this.handleCancel} onCapture={this.handleCapture} onSubmit={this.handleSubmit} diff --git a/src/lib/camera.js b/src/lib/camera.js index 981ee972d2d5115434a933a55239bc562c49511d..4b6a08e7a3506bbf84688a92473a94f0e10df524 100644 --- a/src/lib/camera.js +++ b/src/lib/camera.js @@ -1,6 +1,29 @@ import getUserMedia from 'get-user-media-promise'; import log from './log.js'; +const requestStack = []; +// Single Setup For All Video Streams +const requestVideoStream = videoDesc => { + let streamPromise; + if (requestStack.length === 0) { + streamPromise = getUserMedia({ + audio: false, + video: videoDesc + }); + requestStack.push(streamPromise); + } else if (requestStack.length > 0) { + streamPromise = requestStack[0]; + requestStack.push(true); + } + return streamPromise; +}; + +const requestDisableCheck = () => { + requestStack.pop(); + if (requestStack.length > 0) return false; + return true; +}; + class StageVideoProvider { constructor (runtime) { /** @@ -122,11 +145,12 @@ class StageVideoProvider { _teardown () { // we might be asked to re-enable before _teardown is called, just ignore it. if (this.enabled === false) { + const disableTrack = requestDisableCheck(); this._disablePreview(); this._singleSetup = null; // by clearing refs to video and track, we should lose our hold over the camera this._video = null; - if (this._track) { + if (this._track && disableTrack) { this._track.stop(); } this._track = null; @@ -241,12 +265,9 @@ class StageVideoProvider { return this._singleSetup; } - this._singleSetup = getUserMedia({ // navigator.mediaDevices.getUserMedia({ - audio: false, - video: { - width: {min: 480, ideal: 640}, - height: {min: 360, ideal: 480} - } + this._singleSetup = requestVideoStream({ // navigator.mediaDevices.getUserMedia({ + width: {min: 480, ideal: 640}, + height: {min: 360, ideal: 480} }) .then(stream => { this._video = document.createElement('video'); @@ -385,7 +406,6 @@ class ModalVideoProvider { /** * Captured image data */ - this._capture = null; /** * Cache frames for this many ms. @@ -406,13 +426,6 @@ class ModalVideoProvider { this._track = null; } - static get FORMAT_IMAGE_DATA () { - return 'image-data'; - } - - static get FORMAT_CANVAS () { - return 'canvas'; - } /** * Dimensions the video stream is analyzed at after its rendered to the @@ -427,34 +440,12 @@ class ModalVideoProvider { this._video = video; } - get videoReady () { - // if (!this.enabled) { - // return false; - // } - if (!this._video) { - return false; - } - if (!this._track) { - return false; - } - const {videoWidth, videoHeight} = this._video; - if (typeof videoWidth !== 'number' || typeof videoHeight !== 'number') { - return false; - } - if (videoWidth === 0 || videoHeight === 0) { - return false; - } - return true; - } enableVideo () { const thisContext = this; this._video = this._video ? this._video : document.createElement('video'); // TODO possibly make common function for this - getUserMedia({ - audio: false, - video: true - }) + requestVideoStream(true) .then(userMediaStream => { try { thisContext._video.srcObject = userMediaStream; @@ -463,20 +454,15 @@ class ModalVideoProvider { } thisContext._track = userMediaStream.getTracks()[0]; - const width = 960; // abstract this out - const height = 720; + thisContext._width = 960; // abstract this out + thisContext._height = 720; - const ctx = thisContext._canvas.getContext('2d'); + const ctx = this._canvas.getContext('2d'); ctx.scale(-1, 1); - ctx.translate(width * -1, 0); + ctx.translate(thisContext._width * -1, 0); - thisContext._videoFeedInterval = setInterval(() => ctx.drawImage(thisContext._video, - // source x, y, width, height - 0, 0, thisContext._video.videoWidth, thisContext._video.videoHeight, - // dest x, y, width, height - 0, 0, width, height - ), thisContext._frameCacheTimeout); + thisContext._drawFrames(); // The following also works... // thisContext._videoFeedInterval = setInterval( @@ -487,50 +473,27 @@ class ModalVideoProvider { // thisContext._setupPreview(); }) .catch(e => { - log.warn(e); // TODO make common function for this + log.warn(e); }); } - // _setupPreview () { - // - // // if we haven't already created and started a preview frame render loop, do so - // if (!this._renderPreviewFrame) { - // - // this._renderPreviewFrame = () => { - // clearTimeout(this._renderPreviewTimeout); - // if (!this._renderPreviewFrame) { - // return; - // } - // - // this._renderPreviewTimeout = setTimeout(this._renderPreviewFrame, this._frameCacheTimeout); - // - // const canvas = this.getFrame({format: ModalVideoProvider.FORMAT_CANVAS}); - // - // if (!canvas) { - // // this._skin.clear(); - // return; - // } - // - // // const xOffset = ModalVideoProvider.DIMENSIONS[0] / -2; - // // const yOffset = ModalVideoProvider.DIMENSIONS[1] / 2; - // // this._skin.drawStamp(canvas, xOffset, yOffset); - // // this.runtime.requestRedraw(); - // }; - // - // this._renderPreviewFrame(); - // } - // } + _drawFrames () { + this._videoFeedInterval = setInterval(() => + this._canvas.getContext('2d').drawImage(this._video, + // source x, y, width, height + 0, 0, this._video.videoWidth, this._video.videoHeight, + // dest x, y, width, height + 0, 0, this._width, this._height + ), this._frameCacheTimeout); + } takeSnapshot () { clearInterval(this._videoFeedInterval); - - // clearTimeout(this._renderPreviewTimeout); - return this._canvas.toDataURL('image/png'); } - getSnapshot () { - return this._capture; + clearSnapshot () { + this._drawFrames(); } /** @@ -636,8 +599,12 @@ class ModalVideoProvider { // } disableVideo () { + // Don't need to do anything with this check, + // but we do want to pop our use of the stream off the stack + const disableTrack = requestDisableCheck(); + if (this._video) { - if (this._track) this._track.stop(); + if (this._track && disableTrack) this._track.stop(); this._video.pause(); this._video.srcObject = null; }