Explorar el Código

Add preview for videos made with webcam (#2837)

* Add preview for videos made with webcam

* Update webcam and screen capture submit icon & fix prop typo

* Remove console.log

* Disable mirroring when showing recorded video

* Fix re-enable of mirroring

* Add discard button

* Don't forget to re-enable mirroring for discard button
Merlijn Vos hace 4 años
padre
commit
cd3b3967da

+ 10 - 3
packages/@uppy/screen-capture/src/SubmitButton.js

@@ -14,9 +14,16 @@ module.exports = function SubmitButton ({ recording, recordedVideo, onSubmit, i1
         onClick={onSubmit}
         data-uppy-super-focusable
       >
-        <svg aria-hidden="true" focusable="false" className="uppy-c-icon" width="48" height="48" viewBox="0 0 48 48">
-          <path d="M0 0h48v48h-48z" fill="none" />
-          <path d="M38.71 20.07c-1.36-6.88-7.43-12.07-14.71-12.07-5.78 0-10.79 3.28-13.3 8.07-6.01.65-10.7 5.74-10.7 11.93 0 6.63 5.37 12 12 12h26c5.52 0 10-4.48 10-10 0-5.28-4.11-9.56-9.29-9.93zm-10.71 5.93v8h-8v-8h-6l10-10 10 10h-6z" />
+        <svg
+          width="12"
+          height="9"
+          viewBox="0 0 12 9"
+          xmlns="http://www.w3.org/2000/svg"
+          aria-hidden="true"
+          focusable="false"
+          className="uppy-c-icon"
+        >
+          <path fill="#fff" fillRule="nonzero" d="M10.66 0L12 1.31 4.136 9 0 4.956l1.34-1.31L4.136 6.38z" />
         </svg>
       </button>
     )

+ 83 - 17
packages/@uppy/webcam/src/CameraScreen.js

@@ -1,8 +1,11 @@
+/* eslint-disable jsx-a11y/media-has-caption */
 const { h, Component } = require('preact')
 const SnapshotButton = require('./SnapshotButton')
 const RecordButton = require('./RecordButton')
 const RecordingLength = require('./RecordingLength')
 const VideoSourceSelect = require('./VideoSourceSelect')
+const SubmitButton = require('./SubmitButton')
+const DiscardButton = require('./DiscardButton')
 
 function isModeAvailable (modes, mode) {
   return modes.indexOf(mode) !== -1
@@ -10,40 +13,103 @@ function isModeAvailable (modes, mode) {
 
 class CameraScreen extends Component {
   componentDidMount () {
-    this.props.onFocus()
+    const { onFocus } = this.props
+    onFocus()
   }
 
   componentWillUnmount () {
-    this.props.onStop()
+    const { onStop } = this.props
+    onStop()
   }
 
   render () {
-    const shouldShowRecordButton = this.props.supportsRecording && (
-      isModeAvailable(this.props.modes, 'video-only')
-      || isModeAvailable(this.props.modes, 'audio-only')
-      || isModeAvailable(this.props.modes, 'video-audio')
+    const {
+      src,
+      recordedVideo,
+      recording,
+      modes,
+      supportsRecording,
+      videoSources,
+      showVideoSourceDropdown,
+      showRecordingLength,
+      onSubmit,
+      i18n,
+      mirror,
+      onSnapshot,
+      onStartRecording,
+      onStopRecording,
+      onDiscardRecordedVideo,
+      recordingLengthSeconds,
+    } = this.props
+
+    const hasRecordedVideo = !!recordedVideo
+    const shouldShowRecordButton = !hasRecordedVideo && supportsRecording && (
+      isModeAvailable(modes, 'video-only')
+      || isModeAvailable(modes, 'audio-only')
+      || isModeAvailable(modes, 'video-audio')
     )
-    const shouldShowSnapshotButton = isModeAvailable(this.props.modes, 'picture')
-    const shouldShowRecordingLength = this.props.supportsRecording && this.props.showRecordingLength
-    const shouldShowVideoSourceDropdown = this.props.showVideoSourceDropdown && this.props.videoSources && this.props.videoSources.length > 1
+    const shouldShowSnapshotButton = !hasRecordedVideo && isModeAvailable(modes, 'picture')
+    const shouldShowRecordingLength = supportsRecording && showRecordingLength
+    const shouldShowVideoSourceDropdown = showVideoSourceDropdown && videoSources && videoSources.length > 1
+
+    const videoProps = {
+      playsinline: true,
+    }
+
+    if (recordedVideo) {
+      videoProps.muted = false
+      videoProps.controls = true
+      videoProps.src = recordedVideo
+
+      // reset srcObject in dom. If not resetted, stream sticks in element
+      if (this.videoElement) {
+        this.videoElement.srcObject = undefined
+      }
+    } else {
+      videoProps.muted = true
+      videoProps.autoplay = true
+      videoProps.srcObject = src
+    }
 
     return (
       <div className="uppy uppy-Webcam-container">
         <div className="uppy-Webcam-videoContainer">
-          <video className={`uppy-Webcam-video  ${this.props.mirror ? 'uppy-Webcam-video--mirrored' : ''}`} autoPlay muted playsinline srcObject={this.props.src || ''} />
+          <video
+            /* eslint-disable-next-line no-return-assign */
+            ref={(videoElement) => (this.videoElement = videoElement)}
+            className={`uppy-Webcam-video  ${mirror ? 'uppy-Webcam-video--mirrored' : ''}`}
+            /* eslint-disable-next-line react/jsx-props-no-spreading */
+            {...videoProps}
+          />
         </div>
         <div className="uppy-Webcam-footer">
           <div className="uppy-Webcam-videoSourceContainer">
-            {shouldShowVideoSourceDropdown ? VideoSourceSelect(this.props) : null}
+            {shouldShowVideoSourceDropdown
+              ? VideoSourceSelect(this.props)
+              : null}
           </div>
           <div className="uppy-Webcam-buttonContainer">
-            {shouldShowSnapshotButton ? SnapshotButton(this.props) : null}
-            {' '}
-            {shouldShowRecordButton ? RecordButton(this.props) : null}
-          </div>
-          <div className="uppy-Webcam-recordingLength">
-            {shouldShowRecordingLength ? RecordingLength(this.props) : null}
+            {shouldShowSnapshotButton && <SnapshotButton onSnapshot={onSnapshot} i18n={i18n} />}
+
+            {shouldShowRecordButton && (
+              <RecordButton
+                recording={recording}
+                onStartRecording={onStartRecording}
+                onStopRecording={onStopRecording}
+                i18n={i18n}
+              />
+            )}
+
+            {hasRecordedVideo && <SubmitButton onSubmit={onSubmit} i18n={i18n} />}
+
+            {hasRecordedVideo && <DiscardButton onDiscard={onDiscardRecordedVideo} i18n={i18n} />}
           </div>
+
+          {shouldShowRecordingLength && (
+            <div className="uppy-Webcam-recordingLength">
+              <RecordingLength recordingLengthSeconds={recordingLengthSeconds} i18n={i18n} />
+            </div>
+          )}
         </div>
       </div>
     )

+ 31 - 0
packages/@uppy/webcam/src/DiscardButton.js

@@ -0,0 +1,31 @@
+const { h } = require('preact')
+
+function DiscardButton ({ onDiscard, i18n }) {
+  return (
+    <button
+      className="uppy-u-reset uppy-c-btn uppy-Webcam-button"
+      type="button"
+      title={i18n('discardRecordedFile')}
+      aria-label={i18n('discardRecordedFile')}
+      onClick={onDiscard}
+      data-uppy-super-focusable
+    >
+      <svg
+        width="13"
+        height="13"
+        viewBox="0 0 13 13"
+        xmlns="http://www.w3.org/2000/svg"
+        aria-hidden="true"
+        focusable="false"
+        className="uppy-c-icon"
+      >
+        <g fill="#FFF" fillRule="evenodd">
+          <path d="M.496 11.367L11.103.76l1.414 1.414L1.911 12.781z" />
+          <path d="M11.104 12.782L.497 2.175 1.911.76l10.607 10.606z" />
+        </g>
+      </svg>
+    </button>
+  )
+}
+
+module.exports = DiscardButton

+ 2 - 2
packages/@uppy/webcam/src/RecordButton.js

@@ -4,7 +4,7 @@ module.exports = function RecordButton ({ recording, onStartRecording, onStopRec
   if (recording) {
     return (
       <button
-        className="uppy-u-reset uppy-c-btn uppy-Webcam-button uppy-Webcam-button--video"
+        className="uppy-u-reset uppy-c-btn uppy-Webcam-button"
         type="button"
         title={i18n('stopRecording')}
         aria-label={i18n('stopRecording')}
@@ -20,7 +20,7 @@ module.exports = function RecordButton ({ recording, onStartRecording, onStopRec
 
   return (
     <button
-      className="uppy-u-reset uppy-c-btn uppy-Webcam-button uppy-Webcam-button--video"
+      className="uppy-u-reset uppy-c-btn uppy-Webcam-button"
       type="button"
       title={i18n('startRecording')}
       aria-label={i18n('startRecording')}

+ 28 - 0
packages/@uppy/webcam/src/SubmitButton.js

@@ -0,0 +1,28 @@
+const { h } = require('preact')
+
+function SubmitButton ({ onSubmit, i18n }) {
+  return (
+    <button
+      className="uppy-u-reset uppy-c-btn uppy-Webcam-button uppy-Webcam-button--submit"
+      type="button"
+      title={i18n('submitRecordedFile')}
+      aria-label={i18n('submitRecordedFile')}
+      onClick={onSubmit}
+      data-uppy-super-focusable
+    >
+      <svg
+        width="12"
+        height="9"
+        viewBox="0 0 12 9"
+        xmlns="http://www.w3.org/2000/svg"
+        aria-hidden="true"
+        focusable="false"
+        className="uppy-c-icon"
+      >
+        <path fill="#fff" fillRule="nonzero" d="M10.66 0L12 1.31 4.136 9 0 4.956l1.34-1.31L4.136 6.38z" />
+      </svg>
+    </button>
+  )
+}
+
+module.exports = SubmitButton

+ 87 - 42
packages/@uppy/webcam/src/index.js

@@ -8,6 +8,7 @@ const supportsMediaRecorder = require('./supportsMediaRecorder')
 const CameraIcon = require('./CameraIcon')
 const CameraScreen = require('./CameraScreen')
 const PermissionsScreen = require('./PermissionsScreen')
+const packageJsonVersion = require('../package.json').version
 
 /**
  * Normalize a MIME type or file extension into a MIME type.
@@ -70,16 +71,18 @@ function getMediaDevices () {
  * Webcam
  */
 module.exports = class Webcam extends Plugin {
-  static VERSION = require('../package.json').version
+  static VERSION = packageJsonVersion
 
   constructor (uppy, opts) {
     super(uppy, opts)
     this.mediaDevices = getMediaDevices()
     this.supportsUserMedia = !!this.mediaDevices
+    // eslint-disable-next-line no-restricted-globals
     this.protocol = location.protocol.match(/https/i) ? 'https' : 'http'
     this.id = this.opts.id || 'Webcam'
     this.title = this.opts.title || 'Camera'
     this.type = 'acquirer'
+    this.capturedMediaFile = null
     this.icon = () => (
       <svg aria-hidden="true" focusable="false" width="32" height="32" viewBox="0 0 32 32">
         <g fill="none" fillRule="evenodd">
@@ -101,6 +104,8 @@ module.exports = class Webcam extends Plugin {
         noCameraDescription: 'In order to take pictures or record video, please connect a camera device',
         recordingStoppedMaxSize: 'Recording stopped because the file size is about to exceed the limit',
         recordingLength: 'Recording length %{recording_length}',
+        submitRecordedFile: 'Submit recorded file',
+        discardRecordedFile: 'Discard recorded file',
       },
     }
 
@@ -132,19 +137,21 @@ module.exports = class Webcam extends Plugin {
     this.render = this.render.bind(this)
 
     // Camera controls
-    this._start = this._start.bind(this)
-    this._stop = this._stop.bind(this)
-    this._takeSnapshot = this._takeSnapshot.bind(this)
-    this._startRecording = this._startRecording.bind(this)
-    this._stopRecording = this._stopRecording.bind(this)
-    this._oneTwoThreeSmile = this._oneTwoThreeSmile.bind(this)
-    this._focus = this._focus.bind(this)
-    this._changeVideoSource = this._changeVideoSource.bind(this)
+    this.start = this.start.bind(this)
+    this.stop = this.stop.bind(this)
+    this.takeSnapshot = this.takeSnapshot.bind(this)
+    this.startRecording = this.startRecording.bind(this)
+    this.stopRecording = this.stopRecording.bind(this)
+    this.discardRecordedVideo = this.discardRecordedVideo.bind(this)
+    this.submit = this.submit.bind(this)
+    this.oneTwoThreeSmile = this.oneTwoThreeSmile.bind(this)
+    this.focus = this.focus.bind(this)
+    this.changeVideoSource = this.changeVideoSource.bind(this)
 
     this.webcamActive = false
 
     if (this.opts.countdown) {
-      this.opts.onBeforeSnapshot = this._oneTwoThreeSmile
+      this.opts.onBeforeSnapshot = this.oneTwoThreeSmile
     }
 
     this.setPluginState({
@@ -212,12 +219,14 @@ module.exports = class Webcam extends Plugin {
     }
   }
 
-  _start (options = null) {
+  // eslint-disable-next-line consistent-return
+  start (options = null) {
     if (!this.supportsUserMedia) {
       return Promise.reject(new Error('Webcam access not supported'))
     }
 
     this.webcamActive = true
+    this.opts.mirror = true
 
     const constraints = this.getConstraints(options && options.deviceId ? options.deviceId : null)
 
@@ -265,7 +274,7 @@ module.exports = class Webcam extends Plugin {
   /**
    * @returns {object}
    */
-  _getMediaRecorderOptions () {
+  getMediaRecorderOptions () {
     const options = {}
 
     // Try to use the `opts.preferredVideoMimeType` or one of the `allowedFileTypes` for the recording.
@@ -280,9 +289,12 @@ module.exports = class Webcam extends Plugin {
         preferredVideoMimeTypes = restrictions.allowedFileTypes.map(toMimeType).filter(isVideoMimeType)
       }
 
-      const acceptableMimeTypes = preferredVideoMimeTypes.filter((candidateType) => MediaRecorder.isTypeSupported(candidateType)
-          && getFileTypeExtension(candidateType))
+      const filterSupportedTypes = (candidateType) => MediaRecorder.isTypeSupported(candidateType)
+        && getFileTypeExtension(candidateType)
+      const acceptableMimeTypes = preferredVideoMimeTypes.filter(filterSupportedTypes)
+
       if (acceptableMimeTypes.length > 0) {
+        // eslint-disable-next-line prefer-destructuring
         options.mimeType = acceptableMimeTypes[0]
       }
     }
@@ -290,10 +302,10 @@ module.exports = class Webcam extends Plugin {
     return options
   }
 
-  _startRecording () {
+  startRecording () {
     // only used if supportsMediaRecorder() returned true
     // eslint-disable-next-line compat/compat
-    this.recorder = new MediaRecorder(this.stream, this._getMediaRecorderOptions())
+    this.recorder = new MediaRecorder(this.stream, this.getMediaRecorderOptions())
     this.recordingChunks = []
     let stoppingBecauseOfMaxSize = false
     this.recorder.addEventListener('dataavailable', (event) => {
@@ -312,7 +324,7 @@ module.exports = class Webcam extends Plugin {
         if (totalSize > maxSize) {
           stoppingBecauseOfMaxSize = true
           this.uppy.info(this.i18n('recordingStoppedMaxSize'), 'warning', 4000)
-          this._stopRecording()
+          this.stopRecording()
         }
       }
     })
@@ -334,8 +346,8 @@ module.exports = class Webcam extends Plugin {
     })
   }
 
-  _stopRecording () {
-    const stopped = new Promise((resolve, reject) => {
+  stopRecording () {
+    const stopped = new Promise((resolve) => {
       this.recorder.addEventListener('stop', () => {
         resolve()
       })
@@ -355,7 +367,13 @@ module.exports = class Webcam extends Plugin {
       return this.getVideo()
     }).then((file) => {
       try {
-        this.uppy.addFile(file)
+        this.capturedMediaFile = file
+        // create object url for capture result preview
+        this.setPluginState({
+          // eslint-disable-next-line compat/compat
+          recordedVideo: URL.createObjectURL(file.data),
+        })
+        this.opts.mirror = false
       } catch (err) {
         // Logging the error, exept restrictions, which is handled in Core
         if (!err.isRestriction) {
@@ -372,7 +390,26 @@ module.exports = class Webcam extends Plugin {
     })
   }
 
-  _stop () {
+  discardRecordedVideo () {
+    this.setPluginState({ recordedVideo: null })
+    this.opts.mirror = true
+    this.capturedMediaFile = null
+  }
+
+  submit () {
+    try {
+      if (this.capturedMediaFile) {
+        this.uppy.addFile(this.capturedMediaFile)
+      }
+    } catch (err) {
+      // Logging the error, exept restrictions, which is handled in Core
+      if (!err.isRestriction) {
+        this.uppy.log(err, 'error')
+      }
+    }
+  }
+
+  stop () {
     if (this.stream) {
       this.stream.getAudioTracks().forEach((track) => {
         track.stop()
@@ -383,16 +420,20 @@ module.exports = class Webcam extends Plugin {
     }
     this.webcamActive = false
     this.stream = null
+    this.setPluginState({
+      recordedVideo: null,
+    })
   }
 
-  _getVideoElement () {
+  getVideoElement () {
     return this.el.querySelector('.uppy-Webcam-video')
   }
 
-  _oneTwoThreeSmile () {
+  oneTwoThreeSmile () {
     return new Promise((resolve, reject) => {
       let count = this.opts.countdown
 
+      // eslint-disable-next-line consistent-return
       const countDown = setInterval(() => {
         if (!this.webcamActive) {
           clearInterval(countDown)
@@ -412,8 +453,9 @@ module.exports = class Webcam extends Plugin {
     })
   }
 
-  _takeSnapshot () {
+  takeSnapshot () {
     if (this.captureInProgress) return
+
     this.captureInProgress = true
 
     this.opts.onBeforeSnapshot().catch((err) => {
@@ -421,7 +463,7 @@ module.exports = class Webcam extends Plugin {
       this.uppy.info(message, 'error', 5000)
       return Promise.reject(new Error(`onBeforeSnapshot: ${message}`))
     }).then(() => {
-      return this._getImage()
+      return this.getImage()
     }).then((tagFile) => {
       this.captureInProgress = false
       try {
@@ -438,8 +480,8 @@ module.exports = class Webcam extends Plugin {
     })
   }
 
-  _getImage () {
-    const video = this._getVideoElement()
+  getImage () {
+    const video = this.getVideoElement()
     if (!video) {
       return Promise.reject(new Error('No video element found, likely due to the Webcam tab being closed.'))
     }
@@ -495,16 +537,16 @@ module.exports = class Webcam extends Plugin {
     return Promise.resolve(file)
   }
 
-  _focus () {
+  focus () {
     if (!this.opts.countdown) return
     setTimeout(() => {
       this.uppy.info(this.i18n('smile'), 'success', 1500)
     }, 1000)
   }
 
-  _changeVideoSource (deviceId) {
-    this._stop()
-    this._start({ deviceId })
+  changeVideoSource (deviceId) {
+    this.stop()
+    this.start({ deviceId })
   }
 
   updateVideoSources () {
@@ -517,7 +559,7 @@ module.exports = class Webcam extends Plugin {
 
   render () {
     if (!this.webcamActive) {
-      this._start()
+      this.start()
     }
 
     const webcamState = this.getPluginState()
@@ -534,13 +576,16 @@ module.exports = class Webcam extends Plugin {
 
     return (
       <CameraScreen
+        // eslint-disable-next-line react/jsx-props-no-spreading
         {...webcamState}
-        onChangeVideoSource={this._changeVideoSource}
-        onSnapshot={this._takeSnapshot}
-        onStartRecording={this._startRecording}
-        onStopRecording={this._stopRecording}
-        onFocus={this._focus}
-        onStop={this._stop}
+        onChangeVideoSource={this.changeVideoSource}
+        onSnapshot={this.takeSnapshot}
+        onStartRecording={this.startRecording}
+        onStopRecording={this.stopRecording}
+        onDiscardRecordedVideo={this.discardRecordedVideo}
+        onSubmit={this.submit}
+        onFocus={this.focus}
+        onStop={this.stop}
         i18n={this.i18n}
         modes={this.opts.modes}
         showRecordingLength={this.opts.showRecordingLength}
@@ -567,7 +612,7 @@ module.exports = class Webcam extends Plugin {
     if (this.mediaDevices) {
       this.updateVideoSources()
 
-      this.mediaDevices.ondevicechange = (event) => {
+      this.mediaDevices.ondevicechange = () => {
         this.updateVideoSources()
 
         if (this.stream) {
@@ -582,8 +627,8 @@ module.exports = class Webcam extends Plugin {
           })
 
           if (restartStream) {
-            this._stop()
-            this._start()
+            this.stop()
+            this.start()
           }
         }
       }
@@ -592,7 +637,7 @@ module.exports = class Webcam extends Plugin {
 
   uninstall () {
     if (this.stream) {
-      this._stop()
+      this.stop()
     }
 
     this.unmount()

+ 7 - 7
packages/@uppy/webcam/src/index.test.js

@@ -10,7 +10,7 @@ describe('Webcam', () => {
 
       const uppy = new Uppy().use(Webcam)
       expect(
-        uppy.getPlugin('Webcam')._getMediaRecorderOptions().mimeType
+        uppy.getPlugin('Webcam').getMediaRecorderOptions().mimeType
       ).not.toBeDefined()
     })
 
@@ -21,7 +21,7 @@ describe('Webcam', () => {
 
       const uppy = new Uppy().use(Webcam, { preferredVideoMimeType: 'video/webm' })
       expect(
-        uppy.getPlugin('Webcam')._getMediaRecorderOptions().mimeType
+        uppy.getPlugin('Webcam').getMediaRecorderOptions().mimeType
       ).toEqual('video/webm')
     })
 
@@ -32,7 +32,7 @@ describe('Webcam', () => {
 
       const uppy = new Uppy().use(Webcam, { preferredVideoMimeType: 'video/mp4' })
       expect(
-        uppy.getPlugin('Webcam')._getMediaRecorderOptions().mimeType
+        uppy.getPlugin('Webcam').getMediaRecorderOptions().mimeType
       ).not.toBeDefined()
     })
 
@@ -45,7 +45,7 @@ describe('Webcam', () => {
         restrictions: { allowedFileTypes: ['video/mp4', 'video/webm'] },
       }).use(Webcam)
       expect(
-        uppy.getPlugin('Webcam')._getMediaRecorderOptions().mimeType
+        uppy.getPlugin('Webcam').getMediaRecorderOptions().mimeType
       ).toEqual('video/mp4')
     })
 
@@ -58,7 +58,7 @@ describe('Webcam', () => {
         restrictions: { allowedFileTypes: ['video/mp4', 'video/webm'] },
       }).use(Webcam)
       expect(
-        uppy.getPlugin('Webcam')._getMediaRecorderOptions().mimeType
+        uppy.getPlugin('Webcam').getMediaRecorderOptions().mimeType
       ).toEqual('video/webm')
     })
 
@@ -72,7 +72,7 @@ describe('Webcam', () => {
       })
         .use(Webcam, { preferredVideoMimeType: 'video/webm' })
       expect(
-        uppy.getPlugin('Webcam')._getMediaRecorderOptions().mimeType
+        uppy.getPlugin('Webcam').getMediaRecorderOptions().mimeType
       ).toEqual('video/webm')
     })
 
@@ -85,7 +85,7 @@ describe('Webcam', () => {
         restrictions: { allowedFileTypes: ['video/mp4', 'video/webm'] },
       }).use(Webcam)
       expect(
-        uppy.getPlugin('Webcam')._getMediaRecorderOptions().mimeType
+        uppy.getPlugin('Webcam').getMediaRecorderOptions().mimeType
       ).toEqual(undefined)
     })
   })

+ 25 - 16
packages/@uppy/webcam/src/style.scss

@@ -115,30 +115,39 @@
   cursor: pointer;
   transition: all 0.3s;
 
+  &:hover {
+    background-color: darken($red, 5%);
+  }
+
   [data-uppy-theme="dark"] & {
     @include blue-border-focus--dark;
   }
 }
 
-  .uppy-Webcam-button svg {
-    width: 30px;
-    height: 30px;
-    max-width: 100%;
-    max-height: 100%;
-    display: inline-block;
-    vertical-align: text-top;
-    overflow: hidden;
-    fill: currentColor;
-  }
+.uppy-Webcam-button--submit {
+  background-color: $blue;
+  margin: 0 12px;
 
-  .uppy-size--md .uppy-Webcam-button {
-    width: 60px;
-    height: 60px;
+  &:hover {
+    background-color: darken($blue, 5%);
   }
+}
 
-  .uppy-Webcam-button:hover {
-    background-color: darken($red, 5%);
-  }
+.uppy-Webcam-button svg {
+  width: 30px;
+  height: 30px;
+  max-width: 100%;
+  max-height: 100%;
+  display: inline-block;
+  vertical-align: text-top;
+  overflow: hidden;
+  fill: currentColor;
+}
+
+.uppy-size--md .uppy-Webcam-button {
+  width: 60px;
+  height: 60px;
+}
 
 .uppy-Webcam-button--picture {
   margin-right: 12px;