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.
Related
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;
I have an iframe element inside my component and I need to somehow detect when the URL of that iframe changes. What I have tried so far:
Added onLoad callback, but this does not trigger every time I redirect in the iframe
Used React.useCallback but also does not work as I wanted to
Create timeout and get the URL from the iframe every x seconds
Minimalistic code example below
export const XComponent = (props: XComponentProps) => {
const ref = React.useRef<any>();
1.
const onLoad = () => {
const url = ref.current.contentWindow.location.href;
// do stg with url
}
2.
const getRef = React.useCallback((node: any) => {
// store node into state, this was not triggered properly either
}, []);
3.
React.useEffect(() => {
const interval = setInterval(() => {
const url = ref.current.contentWindow.location.href;
}, 1000);
return () => clearInterval(interval);
}, []);
return (
<div className={ styles.albertStoremanTab }>
<div className={ styles.container }>
<iframe id={"iframe-my-id"} onLoad={onLoad} src={props.defaultUrl} ref={ref}></iframe>
</div>
</div>
);
};
That's simple:
useEffect(() => {
// It runs only when defaultUrl changes
}, [props.defaultUrl])
Did you try using ref.current in the dependency array of the useEffect?
useEffect(() => { setURL(ref.current.contentWindow.location.href) },
[ref.current])
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 am trying to implement load more button for my small project GiF generator. First I thought of appending next set of 20 response at the bottom, but failed to do.
Next, I thought of implementing loading the next set of 20 results by simply removing the current one. I tried to trigger a method on click of button, but I failed to do so. Its updating the state on second click of load more and then never updating it again.
Please help me find what I am missing, I have started learning React yesterday itself.
import React, { useEffect, useState } from 'react';
import './App.css';
import Gif from './Gif/Gif';
const App = () => {
const API_KEY = 'LIVDSRZULELA';
const [gifs, setGif] = useState([]);
const [search, setSearch] = useState('');
const [query, setQuery] = useState('random');
const [limit, setLimit] = useState(20);
const [pos, setPos] = useState(1);
useEffect(() => {
getGif();
}, [query])
const getGif = async () => {
const response = await fetch(`https://api.tenor.com/v1/search?q=${query}&key=${API_KEY}&limit=${limit}&pos=${pos}`);
const data = await response.json();
setGif(data.results);
console.log(data.results)
}
const updateSearch = e => {
setSearch(e.target.value);
}
const getSearch = e => {
e.preventDefault();
setQuery(search);
setSearch('');
}
const reload = () => {
setQuery('random')
}
const loadMore = () => { // this is where I want my Pos to update with 21 on first click 41 on second and so on
let temp = limit + 1 + pos;
setPos(temp);
setQuery(query);
}
return (
<div className="App">
<header className="header">
<h1 className="title" onClick={reload}>React GiF Finder</h1>
<form onSubmit={getSearch} className="search-from">
<input className="search-bar" type="text" value={search}
onChange={updateSearch} placeholder="type here..." />
<button className="search-button" type="submit">Search</button>
</form>
<p>showing results for <span>{query}</span></p>
</header>
<div className="gif">
{gifs.map(gif => (
<Gif
img={gif.media[0].tinygif.url}
key={gif.id}
/>
))}
</div>
<button className="load-button" onClick={loadMore}>Load more</button>
</div>
);
}
export default App;
Please, help me find, what I am doing wrong, As I know the moment I will update setQuery useEffect should be called with new input but its not happening.
Maybe try something like this:
// Fetch gifs initially and then any time
// the search changes.
useEffect(() => {
getGif().then(all => setGifs(all);
}, [query])
// If called without a position index, always load the
// initial list of items.
const getGif = async (position = 1) => {
const response = await fetch(`https://api.tenor.com/v1/search?q=${query}&key=${API_KEY}&limit=${limit}&pos=${position}`);
const data = await response.json();
return data.results;
}
// Append new gifs to existing list
const loadMore = () => {
let position = limit + 1 + pos;
setPos(position);
getGif(position).then(more => setGifs([...gifs, ...more]);
}
const getSearch = e => {
e.preventDefault();
setQuery(search);
setSearch('');
}
const updateSearch = e => setSearch(e.target.value);
const reload = () => setQuery('random');
Basically, have the getGifs method be a bit more generic and then if loadMore is called, get the next list of gifs from getGift and append to existing list of gifs.