mighty-webcamjs
Version:
HTML5 Webcam Image Capture Library with Flash Fallback
208 lines (184 loc) • 6.41 kB
JavaScript
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);