'Playing sound in React.js
import React, { Component } from 'react'
import { Button, Input, Icon,Dropdown,Card} from 'semantic-ui-react'
import { Link } from 'react-router-dom'
import $ from 'jquery'
import styles from './Home.scss'
import Modal from './Modal.jsx'
import MakeChannelModal from './MakeChannelModal.jsx'
class Music extends React.Component {
constructor(props) {
super(props);
this.state = {
play: false,
pause: true
};
this.url = "http://streaming.tdiradio.com:8000/house.mp3";
this.audio = new Audio(this.url);
}
play(){
this.setState({
play: true,
pause: false
});
console.log(this.audio);
this.audio.play();
}
pause(){
this.setState({ play: false, pause: true });
this.audio.pause();
}
render() {
return (
<div>
<button onClick={this.play}>Play</button>
<button onClick={this.pause}>Pause</button>
</div>
);
}
}
export default Music
This is the code that I am using to play the sound with url (this.url) in my react app. When I press the play button, it gives me an error
Uncaught TypeError: Cannot read property 'setState' of undefined
I am not sure why this is happpening since I don't see any undefined states. A;; states have been declared.
I am new to react so I might be missing something very important.
Please help!
Solution 1:[1]
A simple approach is by using the useSound hook.
To do this, first install the npm package:
npm install use-sound
Imports:
import useSound from 'use-sound'
import mySound from '../assets/sounds/yourSound.mp3' // Your sound file path here
Use example 1
A simple approach..
function MyButton(){
const [playSound] = useSound(mySound)
return (
<button onClick={playSound}>
Play Sound
</button>
)
}
Use example 2
Less simple than the example 1. In this setup we can control the volume of the sound. Also the playSound() will be triggered inside the handleClick function, allowing you to do more things on click than just playing a sound.
function MyButton(){
const [playSound] = useSound(mySound, { volume: 0.7 }) // 70% of the original volume
const handleClick = () => {
playSound()
// maybe you want to add other things here?
}
return (
<button onClick={() => handleClick()}>
Play Sound
</button>
)
}
For more info click here or here
Solution 2:[2]
I faced a different problem with this implementation of the answer.
It seemed the browser was continuously trying to download the sound on every re-render.
I ended up using useMemo for the Audio with no dependencies which causes the hook to only ever once create the Audio and never attempt to recreate it.
import {useMemo, useEffect, useState} from "react";
const useAudio = url => {
const audio = useMemo(() => 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];
};
export default useAudio;
Solution 3:[3]
I got some problems following these steps when working with Next Js because Audio is HTMLElement tag, eventually, it was rendering me a big fat error, so I decided to study more and the result for it in my project was the following:
//inside your component function.
const [audio] = useState( typeof Audio !== "undefined" && new Audio("your-url.mp3")); //this will prevent rendering errors on NextJS since NodeJs doesn't recognise HTML tags neither its libs.
const [isPlaying, setIsPlaying] = useState(false);
To handle the player, I made a useEffect:
useEffect(() => {
isPlaying ? audio.play() : audio.pause();
}, [isPlaying]);
You will manage the state "isPlaying" according to the functions you make so far.
Solution 4:[4]
Uncaught TypeError: Cannot read property 'setState' of undefined
The error occurs because of how the this keyword works in JavaScript. I think the Audio should play just fine if we solve that issue.
If you do a console.log(this) inside play() you will see that this it is undefined and that's why it throws that error, since you are doing this.setState().Basically the value of this inside play() depends upon how that function is invoked.
There are two common solutions with React:
- Using bind() to set the value of a function's this regardless of how it's called:
constructor(props) {
super(props);
this.play() = this.play.bind(this);
}
- Using arrow functions which don't provide their own this binding
<button onClick={() => {this.play()}}>Play</button>
Now you will have access to this.setState and this.audio inside play(), and the same goes for pause().
Solution 5:[5]
I'm a bit late to the party here but piggy backing off of 'Thomas Hennes':
One problem people looking at this will run into is, if you try to use this code verbatim in an app with multiple pages, they are not going to have a nice time. Since state is managed at the component, you can play, navigate and play again.
To get around that you want to have your component push it's state up to App.js instead and manage the state there.
Allow me to show what I mean.
My player component looks like this:
import React, { Component } from 'react'
class MusicPlayer extends Component {
render() {
const { playing } = this.props.player;
return (
<div>
<button onClick={this.props.toggleMusic.bind(this, playing)}>{playing ? "Pause" : "Play"}</button>
</div>
);
}
};
export default MusicPlayer;
Then in my App.js it looks something like this (using a TODO list sample app):
import React, { Component } from 'react';
import { BrowserRouter as Router, Route } from 'react-router-dom'
import './App.css';
import Header from './componets/layout/Header'
import Todos from './componets/Todos'
import AddTodo from './componets/AddTodo'
import About from './componets/pages/About'
import MusicPlayer from './componets/MusicPlayer'
import axios from 'axios';
class App extends Component {
constructor(props) {
super(props);
this.state = { playing: false, todos: [] }
this.audio = new Audio('<YOUR MP3 LINK HERE>');
}
componentDidMount(){
axios.get('https://jsonplaceholder.typicode.com/todos')
.then(res => this.setState({ playing: this.state.playing, todos: res.data }))
}
toggleComplete = (id) => {
this.setState({ playing: this.state.playing, todos: this.state.todos.map(todo => {
if (todo.id === id){
todo.completed = !todo.completed
}
return todo
}) });
}
delTodo = (id) => {
axios.delete(`https://jsonplaceholder.typicode.com/todos/${id}`)
.then(res => this.setState({ playing: this.state.playing, todos: [...this.state.todos.filter(todo => todo.id !== id)] }));
}
addTodo = (title) => {
axios.post('https://jsonplaceholder.typicode.com/todos', {
title,
completed: false
})
.then(res => this.setState({ playing: this.state.playing, todos: [...this.state.todos, res.data]}))
}
toggleMusic = () => {
this.setState({ playing: !this.state.playing, todos: this.state.todos}, () => {
this.state.playing ? this.audio.play() : this.audio.pause();
});
}
render() {
return (
<Router>
<div className="App">
<div className="container">
<Header />
<Route exact path="/" render={props => (
<React.Fragment>
<AddTodo addTodo={this.addTodo} />
<Todos todos={this.state.todos} toggleComplete={this.toggleComplete} delTodo={this.delTodo} />
</React.Fragment>
)} />
<Route path="/About" render={props => (
<React.Fragment>
<About />
<MusicPlayer player={this.state} toggleMusic={this.toggleMusic} />
</React.Fragment>
)} />
</div>
</div>
</Router>
);
}
}
export default App;
Sources
This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.
Source: Stack Overflow
| Solution | Source |
|---|---|
| Solution 1 | |
| Solution 2 | Titan Chase |
| Solution 3 | Daniel Nascimento |
| Solution 4 | Pablo Corso |
| Solution 5 | mBrice1024 |
