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;
Hooks 版本(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
Example App.js
使用MultiPlayer
成分:
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 个并行数组:
- 您的音频源(由
urls
您传递给父组件的道具;这urls
props 是一个字符串数组(您的 MP3 URL))
- 跟踪每个玩家状态的数组
The toggle
方法根据以下逻辑更新玩家状态数组:
- 如果当前有一个玩家处于活动状态(即音频正在播放)并且该活动玩家不是切换方法的目标玩家,则将该玩家的播放状态恢复为 false,并将目标播放器的播放状态设置为 true [您单击了“播放” ' 尽管another音频流已经在播放]
- 如果当前活动的玩家是切换方法的目标玩家,只需将目标玩家的播放状态恢复为 false [您单击了“暂停”]
- 如果当前没有活动的播放器,只需将目标播放器的状态设置为 true [您在当前没有音频流正在播放时单击“播放”]
请注意,toggle
方法被柯里化以接受源玩家的索引(即单击相应按钮的子组件的索引)。
实际的音频对象控制发生在useEffect
与原始钩子一样,但稍微复杂一些,因为我们必须在每次更新时迭代整个音频对象数组。
类似地,音频流“结束”事件的事件侦听器会在一秒钟内处理useEffect
与原始钩子一样,但更新为处理音频对象数组而不是单个此类对象。
最后,从父级调用新的钩子MultiPlayer
组件(容纳多个玩家),然后映射到个人Player
使用 (a) 一个包含播放器当前状态及其源流 URL 的对象,以及 (b) 使用播放器索引进行柯里化的切换方法。
代码沙盒演示