I am trying to autoplay a video when rendered, but only once. On the hover effect, I would like it to play the video with a loop. I have tried to use event.target.play() on mouseOver but have had no luck.
Any help would be greatly appreciated :)
<video
className="videos"
autoPlay={true}
onMouseOver={event => event.target.play()}
onMouseOut={event => event.target.pause()}
muted={true}
src={prompt}>
</video>
You can use onEnded event along with mouseover to loop it. Also using useRef makes it easy to set/play instead of passing event from all the callbacks. Check below code,
import { useEffect, useRef, useState } from "react";
import testVideo from "./testVideo.mp4";
export default function App() {
const ref = useRef(null);
const [focus, setFocus] = useState(false);
const loop = () => {
ref.current.play();
};
const onEndedLoop = () => {
if (focus) loop(); // when ended check if its focused then loop
};
useEffect(() => {
if (focus) loop(); // when focused then loop
}, [focus]);
return (
<div className="App">
<h2>Loop video with hover ReactJS!</h2>
<video
id="video"
ref={ref}
style={{ width: "300px" }}
autoPlay
onMouseOver={() => setFocus(true)}
onMouseOut={() => setFocus(false)}
muted={true}
src={testVideo}
onEnded={onEndedLoop}
></video>
</div>
);
}
check working demo - https://codesandbox.io/s/loop-video-on-hover-qegu7
#Madhuri has good answer (Which I upvoted).
However, if we add a function to pause loop when not focused, that will loop the video only when in focus and stop when not hovering over or not in focus.
const loop = () => {
ref.current.play();
};
const pauseLoop = () => {
ref.current.pause();
};
const onEndedLoop = () => {
if (focus) loop(); // when ended check if its focused then loop
};
useEffect(() => {
if (focus) loop(); // when focused then loop
if (!focus) pauseLoop(); // when not focused then pause loop
}, [focus]);
Related
[Problem]
I have a HTML Input element to focus on inside my bottomsheet which is hidden by default. I would like to focus on it when bottomsheet is shown, but I am keep missing it.
[What I've tried]
I already tried autoFocus={true} but it didn't work.
I tried the following, still not working.
const bottomSheetPage = (props) => {
const [bottomSheetOn, setBottomSheetOn] = useState(false)
const inputRef = useRef<HTMLInputElement>(null)
~~~ some codes ~~~
useEffect ( () => {
if(props.autoFocus) {
inputRef?.current?.focus()
}
}, [isBottomsheetOn])
~~~ some codes ~~~
<input ref={inputRef}/>
bottomSheetOn is state that controls the toggle of bottomsheet and checked that prop.autoFocus === true.
How can I focus on the element inside bottomsheet when it's shown?
This could have one of two reasons:
props.autoFocus is false
props.autoFocus is true, but useEffect is only called when isBottomsheetOn changes
Try adding props.autoFocus to the list of useEffect dependencies and console.log/debugger inside useEffect to make sure it is called correctly.
useEffect ( () => {
if(props.autoFocus) {
inputRef?.current?.focus()
}
}, [isBottomsheetOn, props.autoFocus]) // will be triggered on props.autoFocus change
If that doesn't help, try to set the focus manually to make sure it's not a problem with the input ref.
import { useState, useEffect, useRef } from 'react'
export default function Component(props) {
const inputRef = useRef(null)
const [on, setOn] = useState(true)
useEffect ( () => {
if (on) {
inputRef?.current?.focus()
}
}, [on])
return (
<>
<input ref={inputRef}/>
<button onClick={() => setOn(prev => !prev)}>Focus</button>
</>
)
}
I found several ways to fix this up, and there was two methods I've actually tried. Either with different advantages.
using IntersectionObserver and setTimeout
you can check if one element intesects other by IntersectionObserver.observe() so I made a recrurring function to check intersection, and then set focus when it's intersecting. Codes is as follows.
const [ticker, setTicker] = useState(true)
const [isIntersecting, setIntersecting] = useState(false)
const observer = new IntersectionObserver(([entry]) => setIntersecting(entry.isIntersecting))
useEffect(() => {
if(props.autoFocus) {
if(inputRef?.current) {
if (isIntersecting) {
inputRef.current.focus()
} else {
setTimerout(() => setTicker(!ticker), 500)
}
}
}
return () => {observer.disconnect()}
}, [ticker])
But this method was focusing on element only once. I needed to focus on it everytime it's shown.
using setTimeout
I figured out that problem was there's time needed for rendering toggled bottomsheet, so I simply gave timeout for focus. And it worked out.
useEffect(() => {
if (focusRef?.current) {
setTimeout(setFocus, 1000)
}
})
const setFocus = () => {
if (focusRef?.current){
focusReft.focus()
}
}
I am trying to play an mp3 using the Audio element but whenever the player renders an error occurs:- Cannot set property 'volume' of undefined.
Play prop is just a boolean value.
The useRef() current property shows me the mp3 file when I console.log it.
I removed the volume property but then it displays the same error for audio.play().
why is the audio undefined?
import React, { useState, useRef } from "react";
import "../../Static/player.css";
import Nowplaying from "./Nowplaying";
import SongInfo from "./SongInfo";
import Slider from "./Slider";
import Duration from "./Duration";
import song from "../../Static/assets/song.mp3";
const Player = (props) => {
const { Play } = props;
const [percentage, setPercentage] = useState(0)
const [duration, setDuration] = useState(0)
const [currentTime, setCurrentTime] = useState(0)
const audioRef = useRef()
const onChange = (e) => {
const audio = audioRef.current
audio.currentTime = (audio.duration / 100) * e.target.value
setPercentage(e.target.value)
}
const play = () => {
const audio = audioRef.current
audio.volume = 0.1
if (!Play) {
audio.play()
}
if (Play) {
audio.pause()
}
}
const getCurrDuration = (e) => {
const percent = ((e.currentTarget.currentTime / e.currentTarget.duration) * 100).toFixed(2)
const time = e.currentTarget.currentTime
setPercentage(+percent)
setCurrentTime(time.toFixed(2))
}
if (Play) {
play();
} else {
play();
}
return (
<div className="player-screen">
<div className="play-screen">
<div className="navbar">
<Nowplaying />
</div>
<div className="song-info">
<SongInfo />
</div>
<div className="player-controls">
<Slider percentage={percentage} onChange={onChange} />
<Duration
duration={duration}
currentTime={currentTime}
/>
<audio
ref={audioRef}
onTimeUpdate={getCurrDuration}
onLoadedData={(e) => {
setDuration(e.currentTarget.duration.toFixed(2));
}}
src={song}
></audio>
</div>
</div>
</div>
);
};
export default Player;
what wrong am I doing?
This part of code:
if (Play) {
play();
} else {
play();
}
gets immediately called, before React even has the chance to set audioRef.current to the Audio element. Your function hasn't even finished rendering yet, React doesn't even know where audioRef is used.
Move that piece of code into a useEffect, or better yet, replace your audioRef with a callback function (which can still store the Audio element in another ref or state variable), as shown here.
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'm trying to get the HTML5 audio element to autoplay on page load. I know that Chrome doesn't allow autoplay by default, unless something triggers it. So I've created a couple of event listeners within the useEffect() hook:
const Audio = () => {
const audioPlayer = document.getElementById('myAudio')
const playAudio = () => audioPlayer.play()
useEffect(() => {
window.addEventListener('scroll', playAudio)
window.addEventListener('mousemove', playAudio)
return () => {
window.removeEventListener('scroll', playAudio)
window.removeEventListener('mousemove', playAudio)
}
}, [])
return (
<audio
id="myAudio"
src={ audioAssetHere }
/>
)
}
This does work but it keeps playing every time the cursor moves. I only want it to play once. I also get this error in the console:
DOMException: play() failed because the user didn't interact with the document first
And yet the audio still works every time the cursor moves.
I also tried using useRef and assign it to the audio tag ref={audioRef}, and then used it within the useEffect hook:
const audioRef = useRef()
const playAudio = () => audioRef.current.play()
useEffect(() => {
window.addEventListener('scroll', playAudio)
window.addEventListener('mousemove', playAudio)
return () => {
window.removeEventListener('scroll', playAudio)
window.removeEventListener('mousemove', playAudio)
}
}, [])
This gives the error:
TypeError: audioRef.current.play is not a function
So in a nutshell, what I want is the audio to play every time the page loads. And I only want it to play once.
Any help would be greatly appreciated. Thanks
you are trying to play audio on every mousemove/scroll event, instead of it you just need to play it once on the first mousemove/scroll event:
const Audio = () => {
const audioRef = React.useRef();
useEffect(() => {
const playAudio = () => {
audioRef.current.play();
removeListeners()
}
window.addEventListener('scroll', playAudio)
window.addEventListener('mousemove', playAudio)
const removeListeners = () => {
window.removeEventListener('scroll', playAudio)
window.removeEventListener('mousemove', playAudio)
}
return () => {
removeListeners()
}
}, [])
return (
<audio
src={ audioAssetHere }
ref={ audioRef }
/>
)
}
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.