-
Notifications
You must be signed in to change notification settings - Fork 40
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
1 changed file
with
74 additions
and
68 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,134 +1,140 @@ | ||
import React, { useCallback, useEffect, useMemo, useRef, useState } from 'react' | ||
import PropTypes from 'prop-types' | ||
import WebWorker from 'web-worker:./worker' | ||
import * as React from "react"; | ||
import PropTypes from "prop-types"; | ||
import WebWorker from "web-worker:./worker"; | ||
|
||
const createWorker = () => new WebWorker() | ||
const createWorker = () => new WebWorker(); | ||
|
||
const stopMediaStream = (stream) => { | ||
if (stream) { | ||
if (stream.getVideoTracks && stream.getAudioTracks) { | ||
stream.getVideoTracks().map(track => { | ||
stream.getVideoTracks().forEach((track) => { | ||
stream.removeTrack(track); | ||
track.stop(); | ||
}); | ||
stream.getAudioTracks().map(track => { | ||
stream.getAudioTracks().forEach((track) => { | ||
stream.removeTrack(track); | ||
track.stop(); | ||
}); | ||
} | ||
else { | ||
} else { | ||
stream.stop(); | ||
} | ||
} | ||
} | ||
}; | ||
|
||
const Reader = (props) => { | ||
const { constraints, onError, onLoad, onScan, resolution, ...other } = props | ||
const constraintsStr = JSON.stringify(constraints) | ||
const { constraints, onError, onLoad, onScan, resolution, ...other } = props; | ||
const constraintsStr = JSON.stringify(constraints); | ||
|
||
const streamRef = useRef(null) | ||
const videoEl = useRef(null) | ||
const canvasEl = useRef(document.createElement('canvas')) | ||
const ctxRef = useRef(null) | ||
const requestRef = useRef() | ||
const [src, setSrc] = useState(null) | ||
const streamRef = React.useRef(null); | ||
const videoEl = React.useRef(null); | ||
const canvasEl = React.useRef(document.createElement("canvas")); | ||
const ctxRef = React.useRef(null); | ||
const requestRef = React.useRef(); | ||
const [src, setSrc] = React.useState(null); | ||
|
||
const isProcessingRef = useRef(false) | ||
const worker = useMemo(createWorker, [createWorker]) | ||
const isProcessingRef = React.useRef(false); | ||
const workerRef = React.useRef(); | ||
|
||
useEffect(() => { | ||
return () => { | ||
worker.terminate() | ||
} | ||
}, [worker]) | ||
React.useEffect(() => { | ||
workerRef.current = createWorker(); | ||
const worker = workerRef.current; | ||
|
||
useEffect(() => { | ||
worker.onmessage = (e) => { | ||
if (onScan) onScan(e.data ? { ...e.data, canvas: canvasEl.current } : null) | ||
isProcessingRef.current = false | ||
} | ||
}, [onScan, worker]) | ||
if (onScan) | ||
onScan(e.data ? { ...e.data, canvas: canvasEl.current } : null); | ||
isProcessingRef.current = false; | ||
}; | ||
|
||
const check = useCallback(() => { | ||
const videoIsPlaying = videoEl.current && videoEl.current.readyState === videoEl.current.HAVE_ENOUGH_DATA | ||
return () => { | ||
worker.terminate(); | ||
}; | ||
}, [onScan]); | ||
|
||
const check = React.useCallback(() => { | ||
const worker = workerRef.current; | ||
|
||
const videoIsPlaying = | ||
videoEl.current && | ||
videoEl.current.readyState === videoEl.current.HAVE_ENOUGH_DATA; | ||
|
||
if (!isProcessingRef.current && videoIsPlaying) { | ||
isProcessingRef.current = true | ||
isProcessingRef.current = true; | ||
|
||
// Get image/video dimensions | ||
let width = videoEl.current.videoWidth | ||
let height = videoEl.current.videoHeight | ||
let width = videoEl.current.videoWidth; | ||
let height = videoEl.current.videoHeight; | ||
|
||
const greatestSize = width > height ? width : height | ||
const ratio = resolution / greatestSize | ||
const greatestSize = width > height ? width : height; | ||
const ratio = resolution / greatestSize; | ||
|
||
height = ratio * height | ||
width = ratio * width | ||
height = ratio * height; | ||
width = ratio * width; | ||
|
||
canvasEl.current.width = width | ||
canvasEl.current.height = height | ||
canvasEl.current.width = width; | ||
canvasEl.current.height = height; | ||
|
||
ctxRef.current = canvasEl.current.getContext('2d') | ||
ctxRef.current.drawImage(videoEl.current, 0, 0, width, height) | ||
const imageData = ctxRef.current.getImageData(0, 0, width, height) | ||
ctxRef.current = canvasEl.current.getContext("2d"); | ||
ctxRef.current.drawImage(videoEl.current, 0, 0, width, height); | ||
const imageData = ctxRef.current.getImageData(0, 0, width, height); | ||
// Send data to web-worker | ||
worker.postMessage(imageData) | ||
worker.postMessage(imageData); | ||
} | ||
|
||
requestRef.current = requestAnimationFrame(check) | ||
}, [resolution, worker]) | ||
requestRef.current = requestAnimationFrame(check); | ||
}, [resolution]); | ||
|
||
useEffect(() => { | ||
const constraints = JSON.parse(constraintsStr) | ||
React.useEffect(() => { | ||
const constraints = JSON.parse(constraintsStr); | ||
|
||
let isSubscribed = true | ||
navigator.mediaDevices.getUserMedia(constraints) | ||
let isSubscribed = true; | ||
navigator.mediaDevices | ||
.getUserMedia(constraints) | ||
.then((stream) => { | ||
if (!isSubscribed) { | ||
stopMediaStream(stream) | ||
stopMediaStream(stream); | ||
} else { | ||
streamRef.current = stream | ||
streamRef.current = stream; | ||
|
||
try { | ||
if (videoEl.current) { | ||
videoEl.current.srcObject = stream | ||
videoEl.current.setAttribute('playsinline', true) // required to tell iOS safari we don't want fullscreen | ||
videoEl.current.srcObject = stream; | ||
videoEl.current.setAttribute("playsinline", true); // required to tell iOS safari we don't want fullscreen | ||
} | ||
} catch (error) { | ||
setSrc(window.URL.createObjectURL(stream)) | ||
setSrc(window.URL.createObjectURL(stream)); | ||
} | ||
|
||
if (onLoad) onLoad() | ||
if (onLoad) onLoad(); | ||
|
||
requestRef.current = requestAnimationFrame(check) | ||
requestRef.current = requestAnimationFrame(check); | ||
} | ||
}) | ||
.catch(error => (isSubscribed ? onError(error) : null)) | ||
.catch((error) => (isSubscribed ? onError(error) : null)); | ||
|
||
return () => { | ||
cancelAnimationFrame(requestRef.current) | ||
isSubscribed = false | ||
stopMediaStream(streamRef.current) | ||
cancelAnimationFrame(requestRef.current); | ||
isSubscribed = false; | ||
stopMediaStream(streamRef.current); | ||
if (src) { | ||
window.URL.revokeObjectURL(src); | ||
} | ||
} | ||
}, [check, constraintsStr, onError, onLoad]) | ||
}; | ||
}, [check, constraintsStr, onError, onLoad, src]); | ||
|
||
return (<video autoPlay playsInline src={src} ref={videoEl} {...other} />) | ||
} | ||
return <video autoPlay playsInline src={src} ref={videoEl} {...other} />; | ||
}; | ||
|
||
Reader.propTypes = { | ||
constraints: PropTypes.object, | ||
onError: PropTypes.func.isRequired, | ||
onLoad: PropTypes.func, | ||
onScan: PropTypes.func.isRequired, | ||
resolution: PropTypes.number, | ||
} | ||
}; | ||
|
||
Reader.defaultProps = { | ||
constraints: { audio: false, video: true }, | ||
resolution: 640, | ||
} | ||
}; | ||
|
||
export default Reader | ||
export default Reader; |