Reputation: 35
I have several buttons on the page, when clicked I want a specific sound to play. It can work with one tag but when I try to add another one (with a different source) it skips to the second tag and will not play the first.
<audio ref={`${cap}`} >
<source src={singleLetter.capitalSound} preload='auto'/>
</audio>
<audio ref={`${low}`} >
<source src={singleLetter.lowerSound} preload='auto'/>
</audio>
<button type="button" className="btn btn-lg display"
onClick={() =>
this.playCapital(cap)
}>
{singleLetter.capital}
</button>
<button type="button" className="btn btn-lg display"
onClick={() =>
this.playLower(low)
}>
{singleLetter.lowercase}
</button>
I am also using the following to play the sounds (its a react/redux app):
playCapital(cap) {
this.refs[cap].load()
this.refs[cap].play()
}
playLower(low) {
this.refs[low].load()
this.refs[low].play()
}
Upvotes: 0
Views: 957
Reputation: 3986
When rendering components to the DOM, React reuses nodes which may cause issues like you have described (especially with <audio>
and <video>
tags). If you really want to use React to play sounds you must explore different approaches - assigning keys, rendering each sound as an independent component, etc.
You should consider using HTML5 Audio API as shown here - it will be clearer and won't require hacks.
Down below are examples of different virtual-dom techniques - each with different effects. The first example renderes each sound as separate audio tags and reference them by url. This is probably what you want to get:
const sets = [
{
capital: 'Play WinXP error',
capitalSound: 'https://www.myinstants.com/media/sounds/erro.mp3',
lowercase: 'Play MSG',
lowerSound: 'https://www.myinstants.com/media/sounds/metalgearsolid.swf.mp3'
},
{
capital: 'Play Leeroy Jenking',
capitalSound: 'https://www.myinstants.com/media/sounds/leroy.swf.mp3',
lowercase: 'Play Murlock',
lowerSound: 'https://www.myinstants.com/media/sounds/sound-9_____.mp3'
}
]
class Example extends React.Component {
constructor() {
super()
this.state = {
set: 0,
sounds: [
sets[0].capitalSound,
sets[0].lowerSound,
]
}
this.playSound = this.playSound.bind(this)
}
playSound(sound) {
this.refs[sound].load()
this.refs[sound].play()
}
render() {
const singleLetter = sets[this.state.set]
return (
<div>
{this.state.sounds.map(sound =>
<audio key={sound} ref={`${sound}`} >
<source src={sound} preload='auto'/>
</audio>
)}
<button type="button" className="btn btn-lg display"
onClick={() =>
this.playSound(singleLetter.capitalSound)
}>
{singleLetter.capital}
</button>
<button type="button" className="btn btn-lg display"
onClick={() =>
this.playSound(singleLetter.lowerSound)
}>
{singleLetter.lowercase}
</button>
<button onClick={() =>
this.setState({
sounds: this.state.set == 0 ?
[
...this.state.sounds,
sets[1].capitalSound,
sets[1].lowerSound
]
:
[
...this.state.sounds,
sets[0].capitalSound,
sets[0].lowerSound
],
set: this.state.set == 0 ? 1 : 0
})
}>
Change set
</button>
</div>
)
}
}
ReactDOM.render(
<Example />,
document.getElementById("app")
);
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react-dom.min.js"></script>
<div id="app"></div>
Example without keys, same sounds will stop playback after being loaded and played when the set changes. (change set -> play Leeroy -> change set -> play WinXP -- Leeroy stops)
const sets = [
{
capital: 'Play WinXP error',
capitalSound: 'https://www.myinstants.com/media/sounds/erro.mp3',
lowercase: 'Play MSG',
lowerSound: 'https://www.myinstants.com/media/sounds/metalgearsolid.swf.mp3'
},
{
capital: 'Play Leeroy Jenking',
capitalSound: 'https://www.myinstants.com/media/sounds/leroy.swf.mp3',
lowercase: 'Play Murlock',
lowerSound: 'https://www.myinstants.com/media/sounds/sound-9_____.mp3'
}
]
class Example extends React.Component {
constructor() {
super()
this.state = {set: 0}
this.playCapital = this.playCapital.bind(this)
this.playLower = this.playLower.bind(this)
}
playCapital(cap) {
this.refs[cap].load()
this.refs[cap].play()
}
playLower(low) {
this.refs[low].load()
this.refs[low].play()
}
render() {
const cap = 'cap'
const low = 'low'
const singleLetter = sets[this.state.set]
return (
<div>
<audio ref={`${cap}`} >
<source src={singleLetter.capitalSound} preload='auto'/>
</audio>
<audio ref={`${low}`} >
<source src={singleLetter.lowerSound} preload='auto'/>
</audio>
<button type="button" className="btn btn-lg display"
onClick={() =>
this.playCapital(cap)
}>
{singleLetter.capital}
</button>
<button type="button" className="btn btn-lg display"
onClick={() =>
this.playLower(low)
}>
{singleLetter.lowercase}
</button>
<button onClick={() =>
this.setState({set: this.state.set == 0 ? 1 : 0})
}>
Change set
</button>
</div>
)
}
}
ReactDOM.render(
<Example />,
document.getElementById("app")
);
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react-dom.min.js"></script>
<div id="app"></div>
Example with keys, immediately sounds stops when the set changes due to React considering those tags being different and not reusing <audio>
tags. (change the set while playing sound to observe that the audio tag gets removed)
const sets = [
{
capital: 'Play WinXP error',
capitalSound: 'https://www.myinstants.com/media/sounds/erro.mp3',
lowercase: 'Play MSG',
lowerSound: 'https://www.myinstants.com/media/sounds/metalgearsolid.swf.mp3'
},
{
capital: 'Play Leeroy Jenking',
capitalSound: 'https://www.myinstants.com/media/sounds/leroy.swf.mp3',
lowercase: 'Play Murlock',
lowerSound: 'https://www.myinstants.com/media/sounds/sound-9_____.mp3'
}
]
class Example extends React.Component {
constructor() {
super()
this.state = {set: 0}
this.playCapital = this.playCapital.bind(this)
this.playLower = this.playLower.bind(this)
}
playCapital(cap) {
this.refs[cap].load()
this.refs[cap].play()
}
playLower(low) {
this.refs[low].load()
this.refs[low].play()
}
render() {
const cap = 'cap'
const low = 'low'
const singleLetter = sets[this.state.set]
return (
<div>
<audio key={singleLetter.capitalSound} ref={`${cap}`} >
<source src={singleLetter.capitalSound} preload='auto'/>
</audio>
<audio key={singleLetter.lowerSound} ref={`${low}`} >
<source src={singleLetter.lowerSound} preload='auto'/>
</audio>
<button type="button" className="btn btn-lg display"
onClick={() =>
this.playCapital(cap)
}>
{singleLetter.capital}
</button>
<button type="button" className="btn btn-lg display"
onClick={() =>
this.playLower(low)
}>
{singleLetter.lowercase}
</button>
<button onClick={() =>
this.setState({set: this.state.set == 0 ? 1 : 0})
}>
Change set
</button>
</div>
)
}
}
ReactDOM.render(
<Example />,
document.getElementById("app")
);
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react-dom.min.js"></script>
<div id="app"></div>
Upvotes: 2