I am using the react-webcam to capture images and videos in my react app. I have implemented the Screenshot (via Ref) example:
const videoConstraints = {
width: 1280,
height: 720,
facingMode: "user"
};
const WebcamCapture = () => {
const webcamRef = React.useRef(null);
const capture = React.useCallback(
() => {
const imageSrc = webcamRef.current.getScreenshot();
},
[webcamRef]
);
return (
<>
<Webcam
audio={false}
height={720}
ref={webcamRef}
screenshotFormat="image/jpeg"
width={1280}
videoConstraints={videoConstraints}
/>
<button onClick={capture}>Capture photo</button>
</>
);
};
Works nicely, however it seems to take a few seconds for the stream to start and video to start showing. I want to be able to disable the button whilst this is happening. I have found there is a flag in the webcamRef that states if it is loading:
useEffect(() => {
if (webcamRef.current) {
const camStarted = webcamRef.current.state.hasUserMedia;
debugger;
}
}, [webcamRef]);
In the above useEffect, whilst the video is initialising, the hasUserMedia is false, once it has loaded it changes to true. This sounds like exactly what I need however as it is in a useRef, it doesn't hit the useEffect when it changes.
Is there any kind of neat trick I can implement to be able to identify when this value in the useRef is changed, or anything else that might help me get the functionality I am after?
If you just want to disable the button until the camera has started streaming, you can use a check that can be triggered using the onUserMedia prop of Webcam.
const videoConstraints = {
width: 1280,
height: 720,
facingMode: "user"
};
const WebcamCapture = () => {
const [loadingCam, setLoadingCam] = useState(true);
const webcamRef = React.useRef(null);
const capture = React.useCallback(
() => {
const imageSrc = webcamRef.current.getScreenshot();
},
[webcamRef]
);
const handleUserMedia = () => {
setTimeout(() => {
// timer is optional if the loading is taking some time.
setLoadingCam(false);
}, 1000);
};
return (
<>
<Webcam
audio={false}
height={720}
ref={webcamRef}
screenshotFormat="image/jpeg"
width={1280}
videoConstraints={videoConstraints}
onUserMedia={handleUserMedia}
/>
<button disable={loadingCam} onClick={capture}>Capture photo</button>
</>
);
};
Related
So essentially, I want to play the lottie animation everytime it is tapped. Here is my UI code for the lottie animation:
<Pressable onPress={playGame}>
<LottieView
ref={loseAnimationRef}
style={styles.egg}
source={Lost}
autoPlay={false}
loop={false}
onAnimationFinish={() => {
resetAnimation();
}}
/>
</Pressable>
Here is my state code for the lottie animation:
const loseAnimationRef = useRef(null);
const playGame = async () => {
await mainGameLogicUC();
playAnimation()
};
const playAnimation = () => {
loseAnimationRef.current.play()
}
const resetAnimation = () => {
loseAnimationRef.current.reset()
}
On the first tap, the animation play perfefctly fine. But on all other taps, the animation won't play. I tried pausing the animation in the onAnimationFinish and then resuming it, but that also didn't work. Am I missing something?
EDIT
I got rid of the resetAnimation() in the onAnimationFinish and that solved the initial problem. But the thing is, I want the animation to be reset to the beginning every time. Why does it break when I reset the animation?
Have you tried something like this?
const animationRef = useRef<AnimatedLottieView>()
const isAnimating = useRef<boolean>(false)
const onAnimationPress = () => {
if (!isAnimating.current) {
isAnimating.current = true
animationRef.current.play()
}
}
<TouchableWithoutFeedback onPress={onAnimationPress}>
<AnimatedLottieView
source={source}
ref={animationRef}
autoPlay={false}
loop={false}
onAnimationFinish={() => {
isAnimating.current = false
animationRef.current.reset()
}}
/>
</TouchableWithoutFeedback>
After coming back to this problem a few days later, I found the solution
Playing the lottie animation seems to be considered a side effect, therefore, editing the references to the animations should be done in a useEffect hook
The solution that worked for me:
(again, in this code, I want the animation to reset to the beginning before the user taps the screen screen again.
state code
const isMounted = useRef(false);
const [isWonAnimationShowing, setIsWonAnimationShowing] = useState(false);
const [isAnimationPlaying, setIsAnimationPlaying] = useState(false);
const loseAnimationRef = useRef(null);
const winAnimationRef = useRef(null);
useEffect(() => {
if (isMounted.current) {
if (isAnimationPlaying) {
_playAnimation();
} else {
_resetAnimation();
}
} else {
isMounted.current = true;
}
}, [isAnimationPlaying]);
const playAnimation = () => {
setIsAnimationPlaying(true);
};
const _playAnimation = () => {
if (isWonAnimationShowing) {
winAnimationRef.current.play();
} else {
loseAnimationRef.current.play();
}
};
const resetAnimation = () => {
setIsAnimationPlaying(false);
};
const _resetAnimation = () => {
if (isWonAnimationShowing) {
winAnimationRef.current.reset();
} else {
loseAnimationRef.current.reset();
}
};
UI code
<View style={styles.body}>
<Pressable disabled={isAnimationPlaying} onPress={playGame}>
{isWonAnimationShowing ? (
<LottieView
ref={winAnimationRef}
style={styles.egg}
source={Won}
autoPlay={false}
loop={false}
onAnimationFinish={() => {
resetAnimation();
}}
/>
) : (
<LottieView
ref={loseAnimationRef}
style={styles.egg}
source={Lost}
autoPlay={false}
loop={false}
onAnimationFinish={() => {
resetAnimation();
}}
/>
)}
</Pressable>
</View>
I'm trying to create a function which would stop camera transmition and add this function to my exisitng code. This is the component that I am trying to change into a function:
class WebcamCapture extends React.Component {
setRef = (webcam) => {
this.webcam = webcam;
}
stop = () => {
let stream = this.webcam.video.srcObject;
const tracks = stream.getTracks();
tracks.forEach(track => track.stop());
this.webcam.video.srcObject = null;
};
render() {
return (
<div>
<Webcam
audio={false}
ref={this.setRef}
/>
<br />
<button
onClick={this.stop}
>Stop</button>
</div>
);
}
}
Below is how I tried to incorporate the stop function from the WebcamCapture component, but I get the following error: Cannot read properties of undefined (reading 'srcObject').
const Camera = () => {
const webcamRef = React.useRef(null);
const [imgSrc, setImgSrc] = React.useState(null);
const stop = React.useCallback(() => { //This is how I tried using stop function
let stream = webcamRef.video.srcObject;
const tracks = stream.getTracks();
tracks.forEach(track => track.stop());
webcamRef.video.srcObject = null;
},[webcamRef, setImgSrc]);
const capture = React.useCallback(() => {
const imageSrc = webcamRef.current.getScreenshot();
setImgSrc(imageSrc);
stop() //I would like to call stop() here
}, [webcamRef, setImgSrc]);
return (
<>
<Webcam
audio={false}
ref={webcamRef}
screenshotFormat="image/jpeg"
/>
<button onClick={capture} >Capture photo</button>
{imgSrc && (
<img
src={imgSrc}
/>
)}
</>
)
}
export default Camera
How could I get the stop function from the WebcamCapture component? Any help would be appreciated!
First, there is no need to add Ref in dependency array.
Second, if you are using ref and using "current" for getScreenshot function then you should use current for video object as well.
const Camera = () => {
const webcamRef = React.useRef(null);
const [imgSrc, setImgSrc] = React.useState(null);
const stop = React.useCallback(() => {
let stream = webcamRef.current.video.srcObject;
const tracks = stream.getTracks();
tracks.forEach(track => track.stop());
webcamRef.current.video.srcObject = null;
},[setImgSrc]);
const capture = React.useCallback(() => {
const imageSrc = webcamRef.current.getScreenshot();
setImgSrc(imageSrc);
stop()
}, [webcamRef, setImgSrc]);
return (
<>
<Webcam
audio={false}
ref={webcamRef}
screenshotFormat="image/jpeg"
/>
<button onClick={capture} >Capture photo</button>
{imgSrc && (
<img
src={imgSrc}
/>
)}
</>
)
}
export default Camera
Just making a video recording component that you can replay afterwards to see if you like it, if not you just re-record, and finally it will store it in the database. I'm using react-webcam for this, with some functionality I've found online.
I have a handleDownload function which sets the video tag source with the blob I just recorded. Originally it downloaded the video file when clicking the button, but I want the video to be replayable as soon as I stop a recording. Ideally, I want to use the same react-webcam component, but not sure I can do that, so for now this will do.
It works when I set the function to onClick listener to the button, however, it doesn't work when I call the function inside of handleStopCaptureClick
so I tried to implement a useEffect which causes handleDownload to run after we stop capturing. This doesn't work either - thoughts? Thanks!
import React, {useEffect} from "react";
import Webcam from "react-webcam";
export const WebcamStreamCapture = () => {
const webcamRef = React.useRef(null);
const mediaRecorderRef = React.useRef(null);
const [capturing, setCapturing] = React.useState(false);
const [recordedChunks, setRecordedChunks] = React.useState([]);
const isInitialMount = React.useRef(true);
useEffect(() => {
if (isInitialMount.current) {
isInitialMount.current = false;
} else {
if (!capturing) {
console.log('running handleDownload')
handleDownload();
}
}
}, [capturing])
const handleStartCaptureClick = React.useCallback(() => {
setCapturing(true);
mediaRecorderRef.current = new MediaRecorder(webcamRef.current.stream, {
mimeType: "video/webm"
});
mediaRecorderRef.current.addEventListener(
"dataavailable",
handleDataAvailable
);
mediaRecorderRef.current.start();
}, [webcamRef, setCapturing, mediaRecorderRef]);
const handleDataAvailable = React.useCallback(
({ data }) => {
if (data.size > 0) {
setRecordedChunks((prev) => prev.concat(data));
}
},
[setRecordedChunks]
);
const handleStopCaptureClick = React.useCallback(() => {
mediaRecorderRef.current.stop();
setCapturing(false);
}, [mediaRecorderRef, webcamRef, setCapturing]);
const handleDownload = React.useCallback(() => {
if (recordedChunks.length) {
const blob = new Blob(recordedChunks, {
type: "video/webm"
});
const url = URL.createObjectURL(blob);
const video = document.getElementById("video-replay");
video.src = url
}
}, [recordedChunks]);
return (
<div className="d-flex flex-column align-items-center">
<Webcam audio={false} ref={webcamRef} height={400} width={500}/>
<video id="video-replay" height="400" width="500" controls></video>
{capturing ? (
<button className="btn btn-danger" onClick={handleStopCaptureClick}>Stop Capture</button>
) : (
<button className="btn btn-danger" onClick={handleStartCaptureClick}>Start Capture</button>
)}
{recordedChunks.length > 0 && (
<div>
<button onClick={handleDownload}>Download</button>
</div>
)}
</div>
);
};
Possible Solution
So I caught myself thinking, if the chunks aren't appearing/working during the useEffect either, it must mean that when capturing stops in handleStopCaptureClick it takes the state a while to update, including chunks I suppose. By changing the dependency from 'capturing' to 'recordedChunks' in useEffect, I was successful in making the video appear right after you stop recording.
Solution: By changing the dependency from 'capturing' to 'recordedChunks' in useEffect, I was successful in making the video appear right after you stop recording.
I have a script that fetches random images from a database, then it shows them as the page's backgroundImage. It has also a loader.
The question is, how can I wait for the div's painting to finish before closing the loader? When div receive the background state, and finished painting, I want to close the loader.
const loader = document.querySelector('.loader');
const Main = () => {
const { bgKeys, defaultColor } = React.useContext(DataContext);
const [background, setBackground] = React.useState(null);
const fetchBackground = React.useCallback(async () => {
if (bgKeys.length) {
// Get random image from IndexedDB
const rand = bgKeys[Math.floor(Math.random() * bgKeys.length)];
const bg = await idbAction('backgrounds', 'getOne', rand);
setBackground(bg.image);
// Close the loader
loader.classList.add('loaded');
}
}, [bgKeys]);
React.useEffect(() => {
fetchBackground();
}, [fetchBackground]);
return (
<div style={{ backgroundImage: `url(${background})` }} />
);
};
Thanks to eindbaas, a fellow Redditor, and other sources I forgot where I found them, the code below works by pre-loading the image in an img element (since my div uses css and not src). Then after the image loads (onload event), it calls the closeLoader function that closes the loader when the image somewhat finished painting, inside requestAnimationFrame callbacks.
const loader = document.querySelector('.loader');
const Main = () => {
const { bgKeys, defaultColor } = React.useContext(DataContext);
const [background, setBackground] = React.useState(null);
// Close loader
const closeLoader = () => {
requestAnimationFrame(() => {
requestAnimationFrame(() => {
loader.classList.add('loaded');
console.log('loaded');
});
});
};
const fetchBackground = React.useCallback(async () => {
if (bgKeys.length) {
// Get random image from IndexedDB
const rand = bgKeys[Math.floor(Math.random() * bgKeys.length)];
const bg = await idbAction('backgrounds', 'getOne', rand);
setBackground(bg.image);
}
}, [bgKeys]);
React.useEffect(() => {
fetchBackground();
}, [fetchBackground]);
return (
<div style={{ backgroundImage: `url(${background})` }}>
<img
style={{ display: 'none' }}
onLoad={closeLoader}
src={background}
alt="Wallpaper"
/>
</div>
);
};
I currently have a preview component which has a reloading functionality attached into it using the useState hook. I now want the ability to refresh this component with the same functionality but with an external component. I know that this can be achieved by the useContext API, however i'm struggling to plug it all together.
Context:
const PreviewContext = React.createContext({
handleRefresh: () => null,
reloading: false,
setReloading: () => null
});
const PreviewProvider = PreviewContext.Provider;
PreviewFrame:
const PreviewFrame = forwardRef((props, ref) => {
const { height, width } = props;
const classes = useStyles({ height, width });
return (
<Card className={classes.root} ref={ref}>
<div className={classes.previewWrapper} > {props.children} </div>
<div className={classes.buttonContainer}>
<IconButton label={'Refresh'} onClick={props.toggleReload} />
</div>
</Card>
);
});
PreviewFrameWrapped:
<PreviewFrame
toggleReload={props.toggleReload}
height={props.height}
width={props.width}
ref={frameRef}
>
<PreviewDiv isReloading={props.isReloading} containerRef={containerRef} height={height} width={width} />
</PreviewFrame>
const PreviewDiv = ({ isReloading, containerRef, height, width }) => {
const style = { height: `${height}px`, width: `${width}px`};
return !isReloading ?
<div className='div-which-holds-preview-content' ref={containerRef} style={style} />
: null;
};
Preview:
export default function Preview(props) {
const [reloading, setReloading] = useState(false);
useEffect(() => {
setReloading(false);
}, [ reloading ]);
const toggleReload = useCallback(() => setReloading(true), []);
return <PreviewFrame isReloading={reloading} toggleReload={toggleReload} {...props} />
}
So now i want to just be able to import the preview component and be able to refresh it using an external button, so not using the one that's already on the <PreviewFrame>.
I ideally want to consume it like this:
import { PreviewContext, PreviewProvider, Preview } from "../../someWhere"
<PreviewProvider>
<Preview />
<PreviewControls />
</PreviewProvider>
function PreviewControls () {
let { handleRefresh } = React.useContext(PreviewContext);
return <div><button onClick={handleRefresh}>↺ Replay</button></div>
}
Preview With My Attempt at Wrapping with Provider:
export default function Preview(props) {
const [reloading, setReloading] = useState(false);
useEffect(() => {
setReloading(false);
}, [ reloading ]);
const toggleReload = useCallback(() => setReloading(true), []);
return (<PreviewProvider value={{ reloading: reloading, setReloading: setReloading, handleRefresh: toggleReload }} >
<PreviewFrame isReloading={reloading} toggleReload={toggleReload} {...props} />
{/* it works if i put the external button called <PreviewControls> here*/}
</PreviewProvider>
);
}
So yeah as i said in the commented out block, it will work if put an external button there, however then that makes it attached/tied to the Preview component itself, I'm really not sure how to transfer the reloading state outside of the Preview into the Provider. Can someone please point out what i'm missing and what i need to do make it work in the way i want to.
All you need to do is to write a custom component PreviewProvider and store in the state of reloading and toggleReload function there. The preview and previewControls can consume it using context
const PreviewContext = React.createContext({
handleRefresh: () => null,
reloading: false,
setReloading: () => null
});
export default function PreviewProvider({children}) {
const [reloading, setReloading] = useState(false);
useEffect(() => {
setReloading(false);
}, [ reloading ]);
const toggleReload = useCallback(() => setReloading(true), []);
return <PreviewContext.Provider value={{reloading, toggleReload}}>{children}</PreviewContext.Provider>
}
export default function Preview(props) {
const {reloading, toggleReload} = useContext(PreviewContext);
return <PreviewFrame isReloading={reloading} toggleReload={toggleReload} {...props} />
}
function PreviewControls () {
let { toggleReload } = React.useContext(PreviewContext);
return <div><button onClick={toggleReload}>↺ Replay</button></div>
}
Finally using it like
import { PreviewContext, PreviewProvider, Preview } from "../../someWhere"
<PreviewProvider>
<Preview />
<PreviewControls />
</PreviewProvider>