janiet
janiet

Reputation: 35

multiple <audio> tags playing different source files

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

Answers (1)

Przemysław Zalewski
Przemysław Zalewski

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

Related Questions