Browse Source

webcam: add `videoConstraints` option (#2362)

* allow webcam to accept constraints as option

* webcam: add typings for `videoConstraints`

* docs: update docs for the new webcam videoConstraints option

* Update website/src/docs/webcam.md

* webcam: prefer videoConstraints.facingMode over facingMode

* webcam: merge in setOptions

* webcam: handle case where constraints cannot be satisfied

Co-authored-by: Kevin Southworth <kevin.southworth@webascender.com>
Renée Kooi 4 years ago
parent
commit
769bd9747d

+ 24 - 8
packages/@uppy/webcam/src/index.js

@@ -147,7 +147,15 @@ module.exports = class Webcam extends Plugin {
   }
 
   setOptions (newOpts) {
-    super.setOptions(newOpts)
+    super.setOptions({
+      ...newOpts,
+      videoConstraints: {
+        // May be undefined but ... handles that
+        ...this.opts.videoConstraints,
+        ...newOpts?.videoConstraints
+      }
+    })
+
     this.i18nInit()
   }
 
@@ -175,9 +183,13 @@ module.exports = class Webcam extends Plugin {
       this.opts.modes.indexOf('video-only') !== -1 ||
       this.opts.modes.indexOf('picture') !== -1
 
+    const videoConstraints = this.opts.videoConstraints ?? {
+      facingMode: this.opts.facingMode
+    }
+
     return {
       audio: acceptsAudio,
-      video: acceptsVideo ? { facingMode: this.opts.facingMode } : false
+      video: acceptsVideo ? videoConstraints : false
     }
   }
 
@@ -204,8 +216,10 @@ module.exports = class Webcam extends Plugin {
         })
         .catch((err) => {
           this.setPluginState({
+            cameraReady: false,
             cameraError: err
           })
+          this.uppy.info(err.message, 'error')
         })
     })
   }
@@ -322,12 +336,14 @@ module.exports = class Webcam extends Plugin {
   }
 
   _stop () {
-    this.stream.getAudioTracks().forEach((track) => {
-      track.stop()
-    })
-    this.stream.getVideoTracks().forEach((track) => {
-      track.stop()
-    })
+    if (this.stream) {
+      this.stream.getAudioTracks().forEach((track) => {
+        track.stop()
+      })
+      this.stream.getVideoTracks().forEach((track) => {
+        track.stop()
+      })
+    }
     this.webcamActive = false
     this.stream = null
   }

+ 1 - 0
packages/@uppy/webcam/types/index.d.ts

@@ -18,6 +18,7 @@ declare module Webcam {
     modes?: WebcamMode[]
     locale?: WebcamLocale
     title?: string
+    videoConstraints?: MediaTrackConstraints
   }
 }
 

+ 20 - 0
packages/@uppy/webcam/types/index.test-d.ts

@@ -1,3 +1,4 @@
+import { expectError } from 'tsd'
 import Uppy = require('@uppy/core')
 import Webcam = require('../')
 
@@ -6,3 +7,22 @@ import Webcam = require('../')
     modes: ['video-only']
   })
 }
+
+{
+  Uppy<Uppy.StrictTypes>().use(Webcam, {
+    modes: ['video-only'],
+    videoConstraints: {
+      width: { min: 420, ideal: 420, max: 1920 },
+      height: { min: 420, ideal: 420, max: 1080 }
+    }
+  })
+}
+
+{
+  expectError(Uppy<Uppy.StrictTypes>().use(Webcam, {
+    modes: ['video-only'],
+    videoConstraints: {
+      width: 'not a number har har'
+    }
+  }))
+}

+ 19 - 3
website/src/docs/webcam.md

@@ -64,7 +64,11 @@ uppy.use(Webcam, {
     'picture'
   ],
   mirror: true,
-  facingMode: 'user',
+  videoConstraints: {
+    facingMode: 'user',
+    width: { min: 720, ideal: 1280, max: 1920 },
+    height: { min: 480, ideal: 800, max: 1080 },
+  },
   showRecordingLength: false,
   preferredVideoMimeType: null,
   preferredImageMimeType: null,
@@ -107,15 +111,27 @@ By default, all modes are allowed, and the Webcam plugin will show controls for
 
 Configures whether or not to mirror preview image from the camera. This option is useful when taking a selfie with a front camera: when you wave your right hand, you will see your hand on the right on the preview screen, like in the mirror. But when you actually take a picture, it will not be mirrored. This is how smartphone selfie cameras behave.
 
-### `facingMode: 'user'`
+### `videoConstraints: {}`
 
-Devices sometimes have multiple cameras, front and back, for example. There is a browser API to set which camera will be used, [facingMode](https://developer.mozilla.org/en-US/docs/Web/API/MediaTrackConstraints/facingMode):
+Configure the kind of video stream you would like to record. Takes an object with properties from the [MediaTrackConstraints][] interface.
+
+You can specify acceptable ranges for the resolution of the video stream using the [`aspectRatio`][], [`width`][], and [`height`][] properties. Each property takes an object with `{ min, ideal, max }` properties. For example, use `width: { min: 720, max: 1920, ideal: 1920 }` to allow any width between 720 and 1920 pixels wide, while preferring the highest resolution.
+
+Devices sometimes have multiple cameras, front and back, for example. [`facingMode`][] lets you specify which should be used:
 
 - `user`: The video source is facing toward the user; this includes, for example, the front-facing camera on a smartphone.
 - `environment`:  The video source is facing away from the user, thereby viewing their environment. This is the back camera on a smartphone.
 - `left`: The video source is facing toward the user but to their left, such as a camera aimed toward the user but over their left shoulder.
 - `right`: The video source is facing toward the user but to their right, such as a camera aimed toward the user but over their right shoulder.
 
+For a full list of available properties, see MDN's [MediaTrackConstraints][] documentation.
+
+[MediaTrackConstraints]: https://developer.mozilla.org/en-US/docs/Web/API/MediaTrackConstraints#Properties_of_video_tracks
+[`aspectRatio`]: https://developer.mozilla.org/en-US/docs/Web/API/MediaTrackConstraints/aspectRatio
+[`width`]: https://developer.mozilla.org/en-US/docs/Web/API/MediaTrackConstraints/width
+[`height`]: https://developer.mozilla.org/en-US/docs/Web/API/MediaTrackConstraints/height
+[`facingMode`]: https://developer.mozilla.org/en-US/docs/Web/API/MediaTrackConstraints/facingMode
+
 ### `showRecordingLength: false`
 
 Configures whether or not to show the length of the recording while the recording is in progress. The default is `false`.