janiet
janiet

Reputation: 35

Using <audio> tags in React app, playing chosen file onClick

I can map through an array and render some images (near the bottom in the generateWord function). I want to be able to click on the image, and play the associated audio file. It plays the second (or last) audio file in the array. I think I should attach some kind of key or index to the audio file so it knows which one to play, but I'm not sure how to exactly. The code below is the component. Part of the state is commented below it so you can see where the mapped array is coming from.

const debug = require("debug")("components:Displayletter")
const React = require("react")
const { connect } = require("react-redux")
const request = require("superagent")
const { Link } = require("react-router")

class Displayletter extends React.Component {

  constructor() {
    super()
    this.playSound = this.playSound.bind(this)
  }

  playSound() {
    this.playWord.play()
  }

  handleClick(e) {
    e.preventDefault()
    this.props.router.push("/")
  }

  render() {
    debug(this.props)
    const { dispatch, letters, letter } = this.props
    const wordsArr = letter.wordImage

    return (
      <div className="row letter-container">
        <div className="col-sm-12">
          {this.generateWord(wordsArr)}
        </div>
      </div>
    )
  }

  generateWord(wordsArr) {
    return wordsArr.map((word) => {
      return (
        <div>
          <audio
          key={word.sound}
          ref={(x) => { this.playWord = x; }}>
            <source src={word.sound} preload='auto'/>
          </audio>
          <img src={word.image} onClick={this.playSound} />
        </div>
      )
    })
  }
}

module.exports = connect(state => state)(Displayletter)

//STATE
  // letters: [
  //   {
  //     id: 1,
  //     capital: "A",
  //     lowercase: "a",
  //     capitalSound: "/sounds/capitalSounds/A.mp3",
  //     lowerSound: "/sounds/lowerSounds/a.mp3",
  //     wordImage: [
  //       {id:1, image:"images/words/aniwaniwa.png", sound: "sounds/ua.mp3"},
  //       {id:2, image:"images/words/anuhe.png", sound: "sounds/anuhe.mp3"},
  //     ],
  //     multimedia: "/multimedia/aniwaniwa.webm",
  //     mediaName: "Aniwaniwa Song",
  //   },

Second iteration - have added an empty object in the constructor and passed a 'word' argument through the onClick and playSound.

generateWord(wordsArr) {
return wordsArr.map((word) => {
  return (
    <div>
    <audio
      key={word}
      ref={(x) => { this.playWords[word] = x; }}>
          <source src={word.sound} preload='auto'/>
    </audio>
      <img src={word.image} onClick={this.playSound.bind(this, word)} />
    </div>
  )
})

  playSound(word) {
    this.playWords[word].play()
  }

  constructor() {
    super()
    this.playCapital = this.playCapital.bind(this)
    this.playLower = this.playLower.bind(this)
    this.playSound = this.playSound.bind(this)
    this.playWords = {}
  }

***************ROUND THREE*******************

const debug = require("debug")("components:Displayletter")
const React = require("react")
const { connect } = require("react-redux")
const request = require("superagent")
const { Link } = require("react-router")

class Displayletter extends React.Component {

  constructor() {
    super()
    this.playCapital = this.playCapital.bind(this)
    this.playLower = this.playLower.bind(this)
    this.playWords = {}
  }

  playCapital() {
    this.playCap.play()
  }

  playLower() {
    this.playLow.play()
  }

  playSound(word) {
    this.playWords[word].play()
  }

  handleClick(e) {
    e.preventDefault()
    this.props.router.push("/")
  }



  render() {
debug(this.props)
const { dispatch, letters, letter } = this.props
const wordsArr = letter.wordImage

    return (
  <div className="row letter-container">
    <div className="col-sm-12">

      <audio
      key={letter.capitalSound}
      ref={(cap) => { this.playCap = cap; }}>
        <source
          src={letter.capitalSound}
          preload="auto" />
        <track
          kind="captions"
          src=""
          srcLang="en" />
      </audio>

      <audio
      key={letter.lowerSound}
      ref={(low) => { this.playLow = low; }}>
        <source
          src={letter.lowerSound}
          preload="auto" />
        <track
          kind="captions"
          src=""
          srcLang="en" />
      </audio>

      <button
        type="button"
        className="btn btn-xl display"
        onClick={this.playCapital}>
        {letter.capital}
      </button>

      <button
        type="button"
        className="btn btn-xl display"
        onClick={this.playLower}>
        {letter.lowercase}
      </button>

    </div>

      <div className="col-sm-12">
    {this.generateWord(wordsArr)}
  </div>

        <div className="col-sm-12">
      <Link key={letter.id} to={`/media/${letter.capital}`}>
        <button
          type="button"
          className="btn"
          onClick={() =>
              dispatch({
                type: "RENDER_LETTER",
                payload: letter,
              })
          }>
          Watch: {letter.mediaName}
        </button>
      </Link>

    </div>
  </div>
)
  }

  generateWord(wordsArr) {
    return wordsArr.map((word) => {
      return (
    <div>
          <audio
        key={word}
        ref={(x) => { this.playWords[word] = x; }}>
          <source src={word.sound} preload='auto'/>
          </audio>
          <img src={word.image} onClick={this.playSound.bind(this, word)} />
        </div>
      )
    })
  }
}

module.exports = connect(state => state)(Displayletter)

  // letters: [
  //   {
  //     id: 1,
  //     capital: "A",
  //     lowercase: "a",
  //     capitalSound: "/sounds/capitalSounds/A.mp3",
  //     lowerSound: "/sounds/lowerSounds/a.mp3",
  //     wordImage: [
  //       {id:1, image:"images/words/aniwaniwa.png", sound: "sounds/ua.mp3"},
  //       {id:2, image:"images/words/anuhe.png", sound: "sounds/anuhe.mp3"},
  //     ],
  //     multimedia: "/multimedia/aniwaniwa.webm",
  //     mediaName: "Aniwaniwa Song",
  //   },

Upvotes: 1

Views: 4524

Answers (1)

Hamms
Hamms

Reputation: 5107

You're actually very close! The problem lies with how you're saving the reference to your audio elements: ref={(x) => { this.playWord = x; }}. Note that because this is inside a map, every element is saving itself, one after the other, as this.playWord, hence why only the last file is getting played when you click.

What you want to do instead is save all your words to an object or array, then reference in your click handler which one you want to play.

First, do something like this.playWords = {} in your constructor, then ref={(x) => { this.playWords[word] = x; }} in render. This will give you an object playWords that stores references to all the audio elements keyed by word.

Then, give playSound a word argument and make sure the onClick passes in that argument by binding it like onClick={this.playSound.bind(this, word)}.

I've put together a simple example that demonstrates that basic structure; you should be able to easily apply these principles to your component.

class Example extends React.Component {
  constructor() {
    super();
    this.words = {};
  }

  logClick(i) {
    console.log("user clicked on", this.words[i]);
  }

  render() {
    const wordsArr = "this is an example sentence".split(" ");
    return (
      <div className="row letter-container">
        <div className="col-sm-12">
          {this.generateWord(wordsArr)}
        </div>
      </div>
    )
  }

  generateWord(wordsArr) {
    return wordsArr.map((word) => {
      return (
        <div>
          <span
            key={word}
            ref={x => { this.words[word] = x; }}
            onClick={this.logClick.bind(this, word)}
          >
            {word}
          </span>
        </div>
      );
    })
  }
}

ReactDOM.render(<Example />, document.getElementById("container"));
<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="container"/>

Upvotes: 3

Related Questions