Ivana
Ivana

Reputation: 842

Starting sound on button click in audio tag in React doesn't functioning

I have main component App, a drum with 9 buttons with letters, on mouse click on each button must be started different sound file. With onClick event and function soundDetectionHandler I am detecting which letter was pressed. When detected, I am setting the state in currentKey variable to that letter/object with keyCode, keyTrigger, id, url. In child component Keypress for each letter I have div in which is embedded audio tag, with no showed own controls. In it there is a button which is onClick calling a audioActivationHandler function in main component, which should start playing of sound file(url) based on detection of class name of audio tag. document.getElementsByClassName("player").play(); Unfortunately it's not working on mouse click, can somebody check, and if this is not the best solution, has anybody to suggest a better solution. Here it is the main component:

import React, { Component } from 'react';
import './App.css';
import Keypress from '.././components/Keypress/Keypress';

class App extends Component {
    constructor(props) {
        super(props)
        this.state = {
            currentKey: [],
                bankTwo : [{
                keyCode: 81,
                keyTrigger: 'Q',
                id: 'Chord-1',
                url: 'https://s3.amazonaws.com/freecodecamp/drums/Chord_1.mp3'
            }, {
                keyCode: 87,
                keyTrigger: 'W',
                id: 'Chord-2',
                url: 'https://s3.amazonaws.com/freecodecamp/drums/Chord_2.mp3'
            }, {
                keyCode: 69,
                keyTrigger: 'E',
                id: 'Chord-3',
                url: 'https://s3.amazonaws.com/freecodecamp/drums/Chord_3.mp3'
            }, {
                keyCode: 65,
                keyTrigger: 'A',
                id: 'Shaker',
                url: 'https://s3.amazonaws.com/freecodecamp/drums/Give_us_a_light.mp3'
            }, {
                keyCode: 83,
                keyTrigger: 'S',
                id: 'Open-HH',
                url: 'https://s3.amazonaws.com/freecodecamp/drums/Dry_Ohh.mp3'
            }, {
                keyCode: 68,
                keyTrigger: 'D',
                id: 'Closed-HH',
                url: 'https://s3.amazonaws.com/freecodecamp/drums/Bld_H1.mp3'
            }, {
                keyCode: 90,
                keyTrigger: 'Z',
                id: 'Punchy-Kick',
                url: 'https://s3.amazonaws.com/freecodecamp/drums/punchy_kick_1.mp3'
            }, {
                keyCode: 88,
                keyTrigger: 'X',
                id: 'Side-Stick',
                url: 'https://s3.amazonaws.com/freecodecamp/drums/side_stick_1.mp3'
            }, {
                keyCode: 67,
                keyTrigger: 'C',
                id: 'Snare',
                url: 'https://s3.amazonaws.com/freecodecamp/drums/Brk_Snr.mp3'
            }],
            isChecked: false
        }
    }

    soundDetectionHandler = (event) => {
        let currentKey = this.state.bankTwo.filter((el, key) => {
            return el.id === event.target.id
        })

        this.setState ({
            currentKey: currentKey
        })
    }

    audioActivationHandler = () => {
        document.getElementsByClassName("player").play();

    }

    checkBoxHandler = () => {
        this.setState({
            isChecked: !this.state.isChecked
        })
    } 

    render () {
        const drum = this.state.bankTwo.map((keypress, key) => {
            return <Keypress
                key={keypress.keyCode}
                keypress={keypress}
                soundDetection={this.soundDetectionHandler}
                activated={this.audioActivationHandler}
            />
            }
        )
        return ( 
            <div className="App">
                <div className="container-fluid">
                    <div className="container">
                        <div className="DrumWrapper">
                            <div className="Drum">
                                {drum}
                            </div>
                            <div className="Controlls">
                                <label className="switch">
                                    <input type="checkbox"
                                        onChange={this.toggleChange}
                                    />
                                    <span className="slider"></span>
                                </label>
                            </div>
                        </div>
                    </div>
                </div>     
            </div>
        )
    }
}
export default App;

Here it is a child stateless component Keypress:

import React from 'react';

const keypress = (props) => {
    return (
        <div id={props.keypress.id} onClick={props.soundDetection}>
        {props.keypress.keyTrigger}
            <audio className="player" src={props.keypress.url}>
                <button onClick={props.activated}>Play</button>
            </audio>
        </div>
    );
}
export default keypress;

Upvotes: 0

Views: 2124

Answers (1)

Fpunkt
Fpunkt

Reputation: 1114

This is what you're after. I refactored your code quite a bit. Making this work in react think was a bit mind-bending I must confess. There are a few comments. Hope this helps :)

app.css:

.player {
  width:100px; 
  height: 100px;
  border: 1px solid black;
  color: black;
  display: flex;
  align-items: center; 
  justify-content:center;
  cursor: pointer;
  box-sizing: border-box;
}

player.js:

import React from 'react'
import './app.css'

export default class Player extends React.Component {
  constructor(props) {
    super(props)
    this.state = {
      active: false,
    }
    this.audio = React.createRef()
  }
  playAudio = () => {
    this.audio.current.play()
  }
  toggleActiveState = () => {
    this.setState({
      active: !this.state.active,
    })
  }
  render() {
    return (
      <div
        className="player"
        style={
          this.state.active
            ? {background: 'black', color: 'white'}
            : {backgroung: 'white', color: 'black'}
        }
        ref={this.ref}
        id={this.props.id}
        onClick={this.props.clicked}>
        <audio
          ref={this.audio}
          src={this.props.url}
          onEnded={this.toggleActiveState}
        />
        {this.props.keyTrigger}
      </div>
    )
  }
}

index.js:

import React from 'react'
import Player from './player'

const page = {
  width: '100vw',
  height: '100vh',
  display: 'flex',
  alignItems: 'center',
  justifyContent: 'center',
}
const keyboard = {
  width: '300px',
  height: '300px',
  display: 'flex',
  flexWrap: 'wrap',
  alignItems: 'center',
  justifyContent: 'center',
  border: '1px solid black',
}

export default class App extends React.Component {
  constructor(props) {
    super(props)
    this.state = {
      currentKey: [],
      bankTwo: [
        {
          keyCode: 81,
          keyTrigger: 'Q',
          id: 'Chord-1',
          url: 'https://s3.amazonaws.com/freecodecamp/drums/Chord_1.mp3',
        },
        {
          keyCode: 87,
          keyTrigger: 'W',
          id: 'Chord-2',
          url: 'https://s3.amazonaws.com/freecodecamp/drums/Chord_2.mp3',
        },
        {
          keyCode: 69,
          keyTrigger: 'E',
          id: 'Chord-3',
          url: 'https://s3.amazonaws.com/freecodecamp/drums/Chord_3.mp3',
        },
        {
          keyCode: 65,
          keyTrigger: 'A',
          id: 'Shaker',
          url:
            'https://s3.amazonaws.com/freecodecamp/drums/Give_us_a_light.mp3',
        },
        {
          keyCode: 83,
          keyTrigger: 'S',
          id: 'Open-HH',
          url: 'https://s3.amazonaws.com/freecodecamp/drums/Dry_Ohh.mp3',
        },
        {
          keyCode: 68,
          keyTrigger: 'D',
          id: 'Closed-HH',
          url: 'https://s3.amazonaws.com/freecodecamp/drums/Bld_H1.mp3',
        },
        {
          keyCode: 90,
          keyTrigger: 'Z',
          id: 'Punchy-Kick',
          url: 'https://s3.amazonaws.com/freecodecamp/drums/punchy_kick_1.mp3',
        },
        {
          keyCode: 88,
          keyTrigger: 'X',
          id: 'Side-Stick',
          url: 'https://s3.amazonaws.com/freecodecamp/drums/side_stick_1.mp3',
        },
        {
          keyCode: 67,
          keyTrigger: 'C',
          id: 'Snare',
          url: 'https://s3.amazonaws.com/freecodecamp/drums/Brk_Snr.mp3',
        },
      ],
      isChecked: false,
      bgColor: 'white',
    }
    this.handleKeyPress = this.handleKeyPress.bind(this)

    this.Q = React.createRef()
    this.W = React.createRef()
    this.E = React.createRef()
    this.A = React.createRef()
    this.D = React.createRef()
    this.S = React.createRef()
    this.Z = React.createRef()
    this.X = React.createRef()
    this.C = React.createRef()
  }

  componentDidMount() {
    document.addEventListener('keydown', this.handleKeyPress)
  }

  componentWillUnmount() {
    document.removeEventListener('keydown', this.handleKeyPress)
  }

  playAudioOnClick = e => {
    e.target.children[0].play()
    this[e.target.id].current.toggleActiveState()

  }

  handleKeyPress = e => {
    if (this.state.isChecked) {
      // in any case uppercase
      let which = e.key.toUpperCase()

      // check if the pressed key is defined as a keyTrigger in the state else do nothing
      if (
        this.state.bankTwo.some(key => {
          return key.keyTrigger === which
        })
      ) {
        // trigger functions in child
        this[which].current.playAudio()
        this[which].current.toggleActiveState()
      }
    }
  }
  handleCheck = () => {
    this.setState({
      isChecked: !this.state.isChecked,
    })
  }

  render() {
    const drum = this.state.bankTwo.map(player => {
      return (
        <Player
          id={player.keyTrigger}
          ref={this[player.keyTrigger]}
          key={player.keyCode}
          keyTrigger={player.keyTrigger}
          url={player.url}
          clicked={this.playAudioOnClick}
        />
      )
    })

    return (
      <section style={page}>
        <main style={keyboard}>{drum}</main>
        <section>
          <label>play with keyboard</label>
          <input type="checkbox" onChange={this.handleCheck} />
        </section>
      </section>
    )
  }
}

It works generally but playing too fast will leave some fields highlighted even if not active. I would love to see a better solution. For a real instrument though I would suggest using the Web Audio API.

Upvotes: 1

Related Questions