在React.js中播放声音 [英] Playing sound in React.js

查看:80
本文介绍了在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

这是我用来在我的react应用程序中播放带有url(this.url)的声音的代码.当我按下播放按钮时,它给我一个错误

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

未捕获的TypeError:无法读取未定义的属性'setState'

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.

请帮助!

推荐答案

我稍微改进了Jaxx的版本,使其包含eventListener,以便在音频结束时重置按钮.

I improved Jaxx's version slightly to include an eventListener so that the button resets when the audio ends.

ES6类属性语法

class Music extends React.Component {
  state = {
    play: false
  }
  audio = new Audio(this.props.url)

  componentDidMount() {
    audio.addEventListener('ended', () => this.setState({ play: false }));
  }

  componentWillUnmount() {
    audio.removeEventListener('ended', () => this.setState({ play: false }));  
  }

  togglePlay = () => {
    this.setState({ play: !this.state.play }, () => {
      this.state.play ? this.audio.play() : this.audio.pause();
    });
  }

  render() {
    return (
      <div>
        <button onClick={this.togglePlay}>{this.state.play ? 'Pause' : 'Play'}</button>
      </div>
    );
  }
}

export default Music;

钩子版本(反应16.8 +):

import React, { useState, useEffect } from "react";

const useAudio = url => {
  const [audio] = useState(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];
};

const Player = ({ url }) => {
  const [playing, toggle] = useAudio(url);

  return (
    <div>
      <button onClick={toggle}>{playing ? "Pause" : "Play"}</button>
    </div>
  );
};

export default Player;

更新03/16/2020:多个并发玩家

响应@Cold_Class的评论:

In response to @Cold_Class's comment:

不幸的是,如果我使用了这些组件中的多个组件,则当我开始播放另一个组件时,来自其他组件的音乐不会停止播放-关于此问题的简单解决方案有何建议?

Unfortunately if I use multiple of these components the music from the other components doesn't stop playing whenever I start another component playing - any suggestions on an easy solution for this problem?

不幸的是,没有使用我们用来实现单个Player组件的确切代码库的简单解决方案.原因是您必须以某种方式将单个播放器状态提升到MultiPlayer父组件,以使toggle功能能够暂停除与您直接交互的播放器之外的其他播放器.

Unfortunately, there is no straightforward solution using the exact codebase we used to implement a single Player component. The reason is that you somehow have to hoist up single player states to a MultiPlayer parent component in order for the toggle function to be able to pause other Players than the one you directly interacted with.

一种解决方案是修改挂钩本身,以同时管理多个音频源.这是一个示例实现:

One solution is to modify the hook itself to manage multiple audio sources concurrently. Here is an example implementation:

import React, { useState, useEffect } from 'react'

const useMultiAudio = urls => {
  const [sources] = useState(
    urls.map(url => {
      return {
        url,
        audio: new Audio(url),
      }
    }),
  )

  const [players, setPlayers] = useState(
    urls.map(url => {
      return {
        url,
        playing: false,
      }
    }),
  )

  const toggle = targetIndex => () => {
    const newPlayers = [...players]
    const currentIndex = players.findIndex(p => p.playing === true)
    if (currentIndex !== -1 && currentIndex !== targetIndex) {
      newPlayers[currentIndex].playing = false
      newPlayers[targetIndex].playing = true
    } else if (currentIndex !== -1) {
      newPlayers[targetIndex].playing = false
    } else {
      newPlayers[targetIndex].playing = true
    }
    setPlayers(newPlayers)
  }

  useEffect(() => {
    sources.forEach((source, i) => {
      players[i].playing ? source.audio.play() : source.audio.pause()
    })
  }, [sources, players])

  useEffect(() => {
    sources.forEach((source, i) => {
      source.audio.addEventListener('ended', () => {
        const newPlayers = [...players]
        newPlayers[i].playing = false
        setPlayers(newPlayers)
      })
    })
    return () => {
      sources.forEach((source, i) => {
        source.audio.removeEventListener('ended', () => {
          const newPlayers = [...players]
          newPlayers[i].playing = false
          setPlayers(newPlayers)
        })
      })
    }
  }, [])

  return [players, toggle]
}

const MultiPlayer = ({ urls }) => {
  const [players, toggle] = useMultiAudio(urls)

  return (
    <div>
      {players.map((player, i) => (
        <Player key={i} player={player} toggle={toggle(i)} />
      ))}
    </div>
  )
}

const Player = ({ player, toggle }) => (
  <div>
    <p>Stream URL: {player.url}</p>
    <button onClick={toggle}>{player.playing ? 'Pause' : 'Play'}</button>
  </div>
)


export default MultiPlayer

使用MultiPlayer组件的示例App.js:

import React from 'react'
import './App.css'
import MultiPlayer from './MultiPlayer'

function App() {
  return (
    <div className="App">
      <MultiPlayer
        urls={[
          'https://www.soundhelix.com/examples/mp3/SoundHelix-Song-1.mp3',
          'https://www.soundhelix.com/examples/mp3/SoundHelix-Song-2.mp3',
          'https://www.soundhelix.com/examples/mp3/SoundHelix-Song-3.mp3',
        ]}
      />
    </div>
  )
}

export default App

这个想法是要管理2个并行数组:

The idea is to manage 2 parallel arrays:

  • 您的音频源(由传递给父组件的urls道具构建; urls道具是一个字符串数组(您的MP3 URL))
  • 一个跟踪每个玩家状态的数组
  • your audio sources (built from the urls props you pass to the parent component ; the urls props is an array of strings (your MP3 URLs))
  • an array tracking the state of each player

toggle方法根据以下逻辑更新播放器状态数组:

The toggle method updates the player state array based on the following logic:

  • 如果当前有一个播放器处于活动状态(例如,正在播放音频),并且该活动播放器不是toggle方法所针对的播放器,则将该播放器的播放状态恢复为false,然后将目标播放器的播放状态设置为true [您单击另一个音频流已经在播放时在播放"上播放]
  • 如果当前活动的播放器是通过toggle方法定位的播放器,只需将目标播放器的播放状态恢复为false [您单击了暂停"]
  • 如果当前没有播放器处于活动状态,只需将目标播放器的状态设置为true [您在没​​有播放音频流的情况下单击播放"即可.
  • if there is a player currently active (i.e. audio is playing) and this active player is not the player targeted by the toggle method, revert that player's playing state to false, and set the targeted player's playing state to true [you clicked on 'play' while another audio stream was already playing]
  • if the player currently active is the player targeted by the toggle method, simply revert the targeted player's playing state to false [you clicked on 'pause']
  • if there is no player currently active, simply set the targeted player's state to true [you clicked on 'play' while no audio stream was currently playing]

请注意,强制使用toggle方法来接受源播放器的索引(即,单击相应按钮的子组件的索引).

Note that the toggle method is curried to accept the source player's index (i.e. the index of the child component where the corresponding button was clicked).

实际的音频对象控制与原始钩子一样在useEffect中发生,但是稍微复杂一些,因为我们每次更新都必须遍历整个音频对象数组.

Actual audio object control happens in useEffect as in the original hook, but is slightly more complex as we have to iterate through the entire array of audio objects with every update.

类似地,音频流结束"事件的事件侦听器在第二个useEffect中进行处理,就像在原始钩子中一样,但是进行了更新以处理音频对象数组,而不是单个音频对象.

Similarly, event listeners for audio stream 'ended' events are handled in a second useEffect as in the original hook, but updated to deal with an array of audio objects rather than a single such object.

最后,从父MultiPlayer组件(容纳多个播放器)调用新的钩子,然后使用(a)包含播放器当前状态及其源流URL的对象将其映射到各个Player (b)使用播放器索引来切换的切换方法.

Finally, the new hook is called from the parent MultiPlayer component (holding multiple players), which then maps to individual Players using (a) an object that contains the player's current state and its source streaming URL and (b) the toggle method curried with the player's index.

CodeSandbox演示

这篇关于在React.js中播放声音的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆