React-Webcam Recording, Replay, Re-recording, Storing - javascript

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.

Related

react-webcam - disable buttons until stream starts

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>
</>
);
};

what is making props behavior inconsistent when set as a variable and passed into another function

I am creating a simple audio player using React.
When pushing the "Play" button, audio will be played from a url and the button will display "Pause". Once the audio is finished the button will reset to "Play."
There are three buttons "one" "two" "three". Each one will load a new audio url to be played by the button. Here is the codesandbox.
I'm new to useEffect and useState and I think there is an issue with how I'm updating this with props.
In my audio player component, if I directly set the url (without updating it with props), the audio will play as expected when the "play" button is pushed. However, when I set the url with props it won't play (even though the console.log() displays the correct url).
here is my Audio3.js component (responsible for playing audio:
import React, { useState, useEffect } from "react";
const useAudio = (url) => {
console.log("in useAudio", url)
const [audio] = useState(new Audio(url));
const [playing, setPlaying] = useState(false);
const toggle = () => setPlaying(!playing);
useEffect(() => {
playing ? audio.play() : audio.pause();
}, [playing]);
useEffect(() => {
audio.addEventListener("ended", () => setPlaying(false));
return () => {
audio.removeEventListener("ended", () => setPlaying(false));
};
}, []);
return [playing, toggle];
};
const Player = (props) => {
console.log("the current page is", props.currPage)
// const url = "https://github.com/cre8ture/audioFilesForBL/blob/main/2.mp3?raw=true"
const url = "https://github.com/cre8ture/audioFilesForBL/blob/main/" +
props.currPage +
".mp3?raw=true"
console.log("the audio 3 is ", url);
const [playing, toggle] = useAudio(url);
return (
<div>
<button onClick={toggle}>{playing ? "Pause" : "Play"}</button>
</div>
);
};
export default Player;
Here is the threeButtons.js file:
import React from "react";
function Tabs(props) {
return (
<>
<button onClick={() => props.handleChangeProps(1)}>ONE</button>
<br />
<button onClick={() => props.handleChangeProps(2)}>TWO</button>
<br />
<button onClick={() => props.handleChangeProps(3)}>THRE</button>
</>
);
}
export default Tabs;
Here is the header component that houses the audio Play button:
import React from "react";
import Audio from "./Audio3";
function Header(props) {
console.log("header", props.currPage);
return (
<Audio currPage={props.currPage} />
);
}
export default Header;
And lastly, here is my App.js
import React, { useState } from "react";
import Header from "./components/header";
import ConceptTabs from "./components/threeButtons";
function App() {
const [pageID, setPageID] = useState(0);
// goes to ConceptTabs
let handleChange = (id) => {
console.log("clicked", id);
handleChange2(id);
setPageID(id);
return id;
};
// Goes to Audio2
let handleChange2 = (id) => {
console.log("clicked2", id);
return id;
};
console.log("pageID", pageID);
return (
<>
<Header currPage={pageID} />
<br />
<ConceptTabs handleChangeProps={handleChange} />
</>
);
}
export default App;
Thanks so much
const [audio] = useState(new Audio(url));
is triggered only once when component mounts. If you want to change it when url change, you'll need to have a useEffect
const audio = useRef(new Audio(url));
useEffect(() => {
audio.current = new Audio(url);
// add listeners
return () => {
if (audio.current) {
// Destroy audio & listeners
}
}
}, [url])
You'll also notice that I've used a useRef instead of useState for your audio, which is better when you need to store something which do not need a "re-render" when changing value

When clicking play again, it plays a new audio on top of the old audio

I am creating an audio player in React and I have the following code. It plays fine when you click on the play button once but refuses to pause when you click pause. Also if you click play again, instead of just continuing the audio it creates new audio of the same song and plays it on top of the previous song. I'm not sure how to fix this.
const AudioPlayer = () => {
let audio = new Audio(song)
const [playing, setPlaying] = useState(false)
const playHandler = () => {
if(playing){
setPlaying(false)
console.log(playing)
}
else{
setPlaying(true)
}
}
const audioPause = () => {
audio.pause()
}
const audioPlay = () => {
audio.play()
}
useEffect (() => {
(playing) ? audioPlay() : audioPause()
}, [playing])
return(
<div className="songPlaying">
<p> { (playing) ? "Now playing" : "Stopped Playing" } </p>
<button type="submit" onClick={playHandler}>
{(playing) ? "Pause" : "Play" }
</button>
</div>
)
}
Instead creating a variable of audio, you can create a state of it, and update it accordingly.
Here is the sample code, this should work in your case:
...
// let audio = new Audio(song)
const [audio] = useState(new Audio(song));
...
In your case, your code is creating a new object every time, when you hit pause and play so this is the reason why it overlapping the audio instead of pause and play.
By creating a state, it remains the same object, so whenever you're updating the state of play and pause, it can use the same audio object and do actions on it, rather than creating a new one.
Here is the minified code of yours:
const AudioPlayer = () => {
const [audio] = useState(new Audio(song));
const [playing, setPlaying] = useState(false)
const toggle = () => setPlaying(!playing);
useEffect(() => {
playing ? audio.play() : audio.pause();
},
[playing]
);
return (
<div className="songPlaying">
<p> {(playing) ? "Now playing" : "Stopped Playing"} </p>
<button type="submit" onClick={toggle}>
{(playing) ? "Pause" : "Play"}
</button>
</div>
)
}
Read this answer for more info.
import React, { useState } from "react";
// Play icon
import voicemailplay from "../../assets/voicemail_play.png";
// Pause icon
import voicemailpause from "../../assets/fax_call_icon.png";
// Global declaration audio array
let audioArray = [];
// Global declaration audio array index
let audioIndex = '';
const VoicemailSider = (props) => {
// Manage flag
const [isPlaying, setIsPlaying] = useState(false);
// Play audio
const playAudio = (audioFilePath, index) => {
if(audioIndex !== '' && audioArray[audioIndex]) {
audioArray[audioIndex].pause();
}
setIsPlaying(
index
);
audioArray = [];
audioIndex = index;
try{
if(index !== '' && event !== '') {
audioArray[index] = new Audio(audioFilePath);
audioArray[index].play();
}
} catch(error) {
console.log("Could not paly this audio - ", error);
}
}
// Pause audio
const pauseAudio = (index) => {
setIsPlaying(
false
);
audioArray[index].pause();
}
return (
<div>
<List
dataSource={voicemaillist} // Your data that comes from databse
renderItem={(item,index) => (
<List.Item key={index}>
<img src={isPlaying === index ? voicemailpause : voicemailplay } alt="voicemail_play" onClick={() => isPlaying !== index ? playAudio(item.audioFilePath, index) : pauseAudio(index) } id={"voicemail_play" + index} style={{width: 35 ,height: 35, margin: "0px 10px"}} />
</List.Item>
)}
</List>
</div>
);
};
export default VoicemailSider;

how can I save image and video file in redux

I am preparing a form that has fields to allow the user to post his picture and a video about her/him(self). To post a picture I prepared a component based on input type file, but to upload a video I have used React Dropzone. Now I want to save this to redux, however when I try to do that redux is complaining that it is the non-serializable item, and when I put it into JSON.Stringify() redux is getting an empty object. What would be the optimal solution for that, I have to store it somewhere in-state, should it be a local state created in this step of the form (fortunately it is the last one) however if the user would like to go back to any previous step and come back this data will be lost
Please advise, the dropzone component is below, onResult is just a handler that takes the value and dispatches an action to redux
import React, { useCallback, useState } from "react";
import { useDropzone } from "react-dropzone";
export const FileDropzone = ({ onResult, fileTypes, maxFiles }) => {
const [progress, setProgress] = useState(0);
const [error, setError] = useState(false);
const onDrop = useCallback(acceptedFiles => {
acceptedFiles.forEach(file => {
const reader = new FileReader();
reader.onerror = () => setError(true);
reader.onprogress = data => {
if (data.lengthComputable) {
var progress = parseInt(
(data.loaded / data.total) * 100,
10
);
setProgress(progress);
}
};
reader.onloadend = () => onResult(reader.result);
reader.readAsArrayBuffer(file);
});
}, []);
const { getRootProps, getInputProps } = useDropzone({
onDrop,
maxFiles,
accept: fileTypes
});
return (
<>
<div
className={`border border-${
error ? "danger" : "light"
} rounded d-flex justify-content-center align-items-center hpx-100`}
{...getRootProps()}
>
<input {...getInputProps()} />
<p>Proszę kliknąć, lub upuścić wybrany plik.</p>
</div>
<div
className="bg-primary hpx-20 mt-1"
style={{ width: `${progress}%` }}
></div>
</>
);
};
Thank you
I think your best bet would be to convert the file to Base64.
Please check How to convert file to base64 in JavaScript?
and
https://github.com/reduxjs/redux/issues/2276

useEffect not initiating audio file function

I'm trying to have a song play in the background of my app when it loads. However, it's not working with useEffect. I've also tried using useState in the useEffect with the same results. In the code below, if I click on the Play button, it works fine. Do you have any pointers on what I'm doing wrong?
First Attempt
const Footer = () => {
let audio = new Audio(onlyYou);
const start = () => {
audio.play();
};
const stop = () => {
audio.pause();
}
useEffect(() => {
start()
}, [])
return (
<footer>
<div>
<button onClick={start}>Play</button>
<button onClick={stop}>Stop</button>
</div>
</footer>
)
Second Attempt
const Footer = () => {
const [playSong, setPlaySong] = useState(false);
let audio = new Audio(onlyYou);
const start = () => {
audio.play();
};
const stop = () => {
audio.pause();
}
useEffect(() => {
setPlaySong(true)
}, [])
return (
<footer>
{playSong && start()}
<div>
<button onClick={start}>Play</button>
<button onClick={stop}>Stop</button>
</div>
</footer>
)
Try this
const audio = new Audio(onlyYou)
const Footer = () => {
useEffect(() => { audio.play() }, [])
return (...)
to see if it works. This has to work first. Otherwise you might run into the problem that audio can't be started without user confirm problem.
How to make audio autoplay on chrome
It may have to do with the fact that the audio file is not loaded and is not ready to play when useEffect runs. Take a look here.

Categories