在 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

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

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

我不确定为什么会发生这种情况,因为我没有看到任何未定义的状态。

我是新来的,所以我可能会错过一些非常重要的东西。

救命啊!

166643 次浏览

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;

钩子版本(React 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 的评论:

不幸的是,如果我使用多个这些组件的其他组件的音乐不停止播放时,我开始播放另一个组件-有什么建议对这个问题的简单解决方案?

不幸的是,没有直接的解决方案使用我们用来实现单个 Player组件的确切代码库。原因是,你必须以某种方式提升单一球员状态到一个 MultiPlayer的父组件,以便 toggle功能能够暂停其他球员,而不是你直接互动的一个。

一种解决方案是修改钩子本身来同时管理多个音频源:

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

这个想法是为了管理两个并行数组:

  • 您的音频源(由传递给父组件的 urls道具构建; urls道具是一个字符串数组(您的 MP3 URL))
  • 跟踪每个玩家状态的数组

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

  • 如果当前有一个活动的播放器(即音频正在播放) ,而这个活动的播放器不是切换方法的目标播放器,将该播放器的播放状态恢复为假,并将目标播放器的播放状态设置为真(当 另一个音频流已经在播放时点击“播放”)
  • 如果当前活动的玩家是切换方法的目标玩家,只需将目标玩家的游戏状态恢复为假(你点击了“暂停”)
  • 如果当前没有玩家活动,只需将目标玩家的状态设置为 true (当没有音频流正在播放时点击“播放”)

请注意,toggle方法被草率地接受源播放器的索引(即点击相应按钮的子组件的索引)。

实际的音频对象控制发生在 useEffect中,就像在最初的钩子中一样,但是稍微复杂一些,因为我们必须在每次更新时迭代整个音频对象数组。

类似地,音频流“结束”事件的事件侦听器在第二个 useEffect中处理,就像在原始钩子中一样,但是更新后处理的是音频对象的数组,而不是这样的单个对象。

最后,从父 MultiPlayer组件(持有多个播放器)调用新的钩子,然后使用(a)一个包含播放器当前状态及其源流 URL 的对象和(b)用播放器索引进行粗略处理的切换方法映射到单个 Player

CodeSandbox 演示

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

出现此错误是因为 这个关键字在 JavaScript 中的工作方式。我认为如果我们解决了这个问题,音频应该可以正常播放。

如果你在 play()中执行 console.log(this),你会看到 this是未定义的,这就是为什么它会抛出这个错误,因为你正在执行 this.setState()。基本上,play()this的值取决于如何调用这个函数。

React 有两种常见的解决方案:

  1. 使用 绑定()来设置函数 this 的值,而不管它是如何调用的:
constructor(props) {
super(props);
this.play() = this.play.bind(this);
}
  1. 使用箭头函数,这些函数不提供自己的绑定
<button onClick={() => {this.play()}}>Play</button>

现在,您可以访问 play()中的 this.setStatethis.audiopause()也可以访问 this.setStatethis.audio

我来得有点晚了,不过我还是继续看《托马斯 · 亨尼斯》(Thomas Hennes) :

查看这些代码的人会遇到的一个问题是,如果您试图在一个具有多个页面的应用程序中一字不差地使用这些代码,那么他们将不会有愉快的时间。因为状态是在组件上管理的,所以您可以播放、导航和再次播放。

为了绕过这个问题,您需要让组件将其状态推送到 App.js,并在那里管理状态。

请允许我说明我的意思。

我的播放器组件是这样的:

import React, { Component } from 'react'


class MusicPlayer extends Component {
render() {
const { playing } = this.props.player;


return (
<div>
<button onClick={this.props.toggleMusic.bind(this, playing)}>{playing ? "Pause" : "Play"}</button>
</div>
);
}
};


export default MusicPlayer;

然后在我的 App.js 中看起来像这样(使用一个 TODO 列表示例应用程序) :

import React, { Component } from 'react';
import { BrowserRouter as Router, Route  } from 'react-router-dom'
import './App.css';
import Header from './componets/layout/Header'
import Todos from './componets/Todos'
import AddTodo from './componets/AddTodo'
import About from './componets/pages/About'
import MusicPlayer from './componets/MusicPlayer'
import axios from 'axios';




class App extends Component {
constructor(props) {
super(props);
this.state = { playing: false, todos: [] }
this.audio = new Audio('<YOUR MP3 LINK HERE>');
}


componentDidMount(){
axios.get('https://jsonplaceholder.typicode.com/todos')
.then(res => this.setState({ playing: this.state.playing, todos: res.data }))
}


toggleComplete = (id) => {
this.setState({ playing: this.state.playing, todos: this.state.todos.map(todo => {
if (todo.id === id){
todo.completed = !todo.completed
}
return todo
}) });
}


delTodo = (id) => {
axios.delete(`https://jsonplaceholder.typicode.com/todos/${id}`)
.then(res => this.setState({ playing: this.state.playing, todos: [...this.state.todos.filter(todo => todo.id !== id)] }));
}


addTodo = (title) => {
axios.post('https://jsonplaceholder.typicode.com/todos', {
title,
completed: false
})
.then(res => this.setState({ playing: this.state.playing, todos: [...this.state.todos, res.data]}))


}


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


render() {
return (
<Router>
<div className="App">
<div className="container">
<Header />
<Route exact path="/" render={props => (
<React.Fragment>
<AddTodo addTodo={this.addTodo} />
<Todos todos={this.state.todos} toggleComplete={this.toggleComplete} delTodo={this.delTodo} />
</React.Fragment>
)} />
<Route path="/About" render={props => (
<React.Fragment>
<About />
<MusicPlayer player={this.state} toggleMusic={this.toggleMusic} />
</React.Fragment>
)} />
</div>
</div>
</Router>
);
}
}


export default App;

在使用 Next Js 时,我遇到了一些问题,因为 Audio 是 HTMLElement 标签,最终,它给我带来了一个很大的错误,所以我决定进一步研究,在我的项目中得到的结果如下:

  //inside your component function.
const [audio] = useState( typeof Audio !== "undefined" && new Audio("your-url.mp3")); //this will prevent rendering errors on NextJS since NodeJs doesn't recognise HTML tags neither its libs.
const [isPlaying, setIsPlaying] = useState(false);

为了对付这个玩家,我做了一个使用效果:

    useEffect(() => {
isPlaying ? audio.play() : audio.pause();
}, [isPlaying]);

您将根据到目前为止创建的函数来管理状态“ isPlay”。

在这个答案的实现中,我遇到了一个不同的问题。

似乎浏览器一直在尝试下载每次重新渲染的声音。

我最终使用 useMemo的音频没有依赖性,这导致挂钩只有一次创建音频,从来没有尝试重新创建它。

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


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


export default useAudio;


您也可以通过使用 useSound钩子来实现这一点。

为了做到这一点,首先安装 npm 包:

npm install use-sound

进口:

import useSound from 'use-sound'
import mySound from '../assets/sounds/yourSound.mp3' // Your sound file path here

用法示例1

一个简单的方法. 。

function MyButton(){
const [playSound] = useSound(mySound)
  

return (
<button onClick={() => playSound()}>
Play Sound
</button>
)
}

用法示例2

在这个设置中,我们可以控制音量。此外,playSound()将在 handleClick()函数内部调用,允许您在单击时做更多的事情,而不仅仅是播放声音。

function MyButton(){
const [playSound] = useSound(mySound, { volume: 0.7 }) // 70% of the original volume
  

const handleClick = () => {
playSound()
// maybe you want to add other things here?
}


return (
<button onClick={() => handleClick()}>
Play Sound
</button>
)
}

了解更多信息 点击这里给你

你可以试试这个,对我有效

var tinung = `${window.location.origin}/terimakasih.ogg`;
var audio = document.createElement("audio");


audio.autoplay = true;
audio.load();
audio.addEventListener(
"load",
function() {
audio.play();
},
true
);
audio.src = tinung;