UNPKG

mighty-webcamjs

Version:

HTML5 Webcam Image Capture Library with Flash Fallback

208 lines (184 loc) 6.41 kB
const React = require('react'); const PropTypes = require('prop-types'); const { findDOMNode } = require('react-dom'); const atom = require('atom-js'); const debounce = require('lodash/debounce'); const BaseComponent = require('./BaseComponent.js'); const navigator = global.navigator || {}; const ua = navigator.userAgent || ''; class WebRTCComponent extends BaseComponent { static propTypes = { webcam: PropTypes.object.isRequired, model: PropTypes.object.isRequired, cssPrefix: PropTypes.string.isRequired, }; static defaultProps = {}; startupWebRTC() { const { webcam } = this.props; const { video } = this; const { STANDARD_RESOLUTIONS, CAPTURE_MODE_VIDEO, DETECTION_MODE_LABELS, } = webcam.constants; // ask user for access to their camera webcam.helpers.detectVideoInputs(webcam.mediaDevices).then(() => { const cameraInfs = webcam.params.get('cameraInfs'); if (!cameraInfs.length) { webcam.dispatch('error', 'Camera not detected.'); } else if (cameraInfs.length > 1) { webcam.switchCamera(webcam.params.get('camera')); } let maxWidth = 9999; const androidVersion = webcam.helpers.getAndroidVersion(); if (androidVersion && webcam.params.get('capture_mode') === CAPTURE_MODE_VIDEO) { // android-only optimisationon so video is not lagging // // NOTE: following code fails when maxWidth is set too low // on some devices (Pixel XL, Huawei p8 lite, Huawei P10), Android 5 devices if (androidVersion < 6) { maxWidth = 1280; } else if (parseInt(androidVersion, 10) === 6) { maxWidth = 1280; } else if (parseInt(androidVersion, 10) === 7) { maxWidth = 1680; } else if (androidVersion >= 8) { maxWidth = 1920; } if (/(HUAWEIVTR-L29|SM-G950|; Pixel|Nexus 6P)/.test(ua)) { maxWidth = 1920; } } // TODO: this code should not be called until webcam.params.get('cameraId') is set. Investigate. let videoConstraints; if (webcam.params.get('cameraDetectionMode') === DETECTION_MODE_LABELS) { // http://stackoverflow.com/a/27444179/2590921 const allSizes = STANDARD_RESOLUTIONS.reduce((result, val) => { if (val <= maxWidth && val <= webcam.params.get('dest_width')) { result.push({ minWidth: val }); } return result; }, [{ sourceId: cameraInfs[webcam.params.get('cameraId')].deviceId } ]); videoConstraints = { optional: allSizes }; } else { videoConstraints = { facingMode: webcam.constants.WEBRTC_CAMERAS[webcam.params.get('camera')], width: { min: STANDARD_RESOLUTIONS[0], ideal: Math.min(webcam.params.get('dest_width'), maxWidth), max: maxWidth }, }; } if (process.env.NODE_ENV !== 'production' && webcam.params.get('verbose')) { console.log('videoConstraints used to getUserMedia', videoConstraints); } webcam.mediaDevices.getUserMedia({ audio: false, video: videoConstraints }).then((stream) => { // mediaDevices.getUserMedia callback fires ONLY when camera access is granted. // detectVideoInputs will provide new list with labels. webcam.helpers.detectVideoInputs(webcam.mediaDevices, true); if (process.env.NODE_ENV !== 'production' && webcam.params.get('verbose')) { console.log('stream from getUserMedia ready.', video); } const run = () => { // got access, attach stream to video video.srcObject = stream; // NOTE: Polyfilled with adapter-js webcam.stream = stream; // TODO: if not working, put it in onloadedmetadata }; if (webcam.params.get('use_ImageCapture_API')) { // Create image capture object and get camera capabilities const imageCapture = new ImageCapture(stream.getVideoTracks()[0]); imageCapture.getPhotoCapabilities() .then((photoCapabilities) => { webcam.params.set({ photoCapabilities }); run(); }) .catch((err) => { console.error('error while ImageCapture.getPhotoCapabilities', err); run(); }); } else { run(); } }).catch((err) => { if (process.env.NODE_ENV !== 'production' && webcam.params.get('verbose')) { console.log('getUserMedia failed', err); } // JH 2016-07-31 Instead of dispatching error, now falling back to Flash if userMedia fails (thx @john2014) // JH 2016-08-07 But only if flash is actually installed -- if not, dispatch error here and now. if (webcam.params.get('enable_flash') && webcam.detectFlash()) { setTimeout(() => { webcam.params.set('force_flash', 1); webcam.reattach(); }); } else { webcam.dispatch('error', err); } }); }); } handleVideoLoadedMetadata = () => { const { webcam } = this.props; webcam.params.set({ load: true, live: true, }); webcam.flip(); } handleUserMediaVideoPlaying = (e) => { const video = e.target; const { webcam } = this.props; let dimensions = { width: video.videoWidth, height: video.videoHeight, }; const debouncedFindBestResolutionCallback = debounce(() => { dimensions = { width: video.videoWidth, height: video.videoHeight, }; webcam.params.set('loadedVideoDimensions', dimensions); }, 250); webcam.findBestResolution(video, dimensions).then(() => { setTimeout( debouncedFindBestResolutionCallback(), 1000 ); }); } handleVideoRef = (element) => { this.video = findDOMNode(element); this.props.onVideoRef(this.video); } componentDidMount() { this.startupWebRTC(); } render() { return ( <video ref={ this.handleVideoRef } className={ `${this.props.cssPrefix}__video` } autoPlay="autoplay" playsInline="playsinline" // THIS IS IMPORTANT FOR iOS11 ! muted={ true } // oncanplay={() => { console.log('video: canplay'); }} // onseeked={() => { console.log('video: seeked'); }} // onseeking={() => { console.log('video: seeking'); }} // onstalled={() => { console.log('video: stalled'); }} // onsuspend={() => { console.log('video: suspend'); }} // onwaiting={() => { console.log('video: onwaiting'); }} // onended={() => { console.log('video: onended'); }} onPlaying={ this.handleUserMediaVideoPlaying } onLoadedMetadata={ this.handleVideoLoadedMetadata } > </video> ); } } module.exports = atom.reactConnect('model', [ 'live', 'use_ImageCapture_API', 'photoCapabilities', 'flash_light_node', ])(WebRTCComponent);