まだコードに直接
/**
* 音频播放组件
*/
import React, { Component } from 'react';
import { Slider, Toast } from 'antd-mobile';
import icon_play from '@/asset/image/panda_book/readpage/icon_play.png';
import icon_pause from '@/asset/image/panda_book/readpage/icon_pause.png';
import './AudioItem.less';
class AudioItem extends Component {
static defaultProps = {
src: '', // 音频地址
}
state = {
isCanPlay: false, // 判断音频是否加载完成
playStatus: false, // 播放状态, true 播放中, false 暂停中,
duration: 0, // 音频的时长
currentDuration: 0, // 当前的播放时长
}
audioItem = null; // 把dom暴露给外部使用
audio = new Audio(); // 一个音频对象
timer = null; // 做一个滑条的防抖
interval = null; // 定时查询播放时的当前时间
// 播放音频
play = () => {
this.audio.play();
this.interval = setInterval(() => {
const time = Math.floor(this.audio.currentTime);
if(time < this.state.duration) {
this.setState({
currentDuration: time
});
} else {
// 播放结束后,直接重置播放时间,停止播放
Toast.info('播放完毕');
clearInterval(this.interval);
this.audio.currentTime = 0;
this.audio.pause();
this.setState({
currentDuration: 0,
playStatus: false
});
}
}, 1000);
}
// 暂停音频
pause = () => {
this.audio.pause();
if(this.interval) {
clearInterval(this.interval);
}
}
// 播放状态切换
handlePlayStatusChange = () => {
const { playStatus, isCanPlay } = this.state;
// 由于ios中不会预加载音频资源,所以只好去掉加载状态的判断,如果有好的建议也可以提
// if(!isCanPlay) {
// Toast.info('音频还没加载完呢~');
// return;
// }
if(!playStatus) {
this.play();
} else {
this.pause();
}
this.setState({
playStatus: !playStatus
});
};
handleSilderChange = (value) => {
if(this.timer) {
clearTimeout(this.timer);
}
// 0.2s之内没有改动就修改当前的时间,做一个播放的防抖
this.timer = setTimeout(() => {
this.pause();
this.audio.currentTime = value;
this.setState({
currentDuration: value
}, () => {
if(this.state.playStatus) {
this.play();
}
})
}, 200);
}
// 根据秒数,返回对应的xx:xx的时间格式
getDurationString = (number) => {
let num = Number(number);
if(isNaN(num) || num <= 0) {
return '00:00';
}
if(num === Infinity) {
return '00:00';
}
if(num < 60) {
return `00:${num.toString().padStart(2, 0)}`;
} else if(num < 3600) {
const minute = Math.floor(num / 60);
const second = num % 60;
return `${minute.toString().padStart(2, 0)}:${second.toString().padStart(2, 0)}`
} else {
const hour = Math.floor(num / 3600);
const minute = Math.floor((num - (hour * 3600)) / 60);
const second = num - (hour * 3600) - (minute * 60);
return `${hour.toString().padStart(2, 0)}:${minute.toString().padStart(2, 0)}:${second.toString().padStart(2, 0)}`;
}
}
init = (props) => {
const { src } = props || this.props;
if(!src) {
return;
}
this.audio.preload = 'automatic';
this.audio.src = src;
this.audio.load();
// this.audio.src = 'http://www.yinpin.com/upload/yingxiaobusanlingfuwu0413017j.mp3';
// 监听音频的时长是否获取到了
this.audio.ondurationchange = () => {
const duration = Math.floor(this.audio.duration);
this.setState({
duration
});
}
// 监听音频是否可以播放了
this.audio.oncanplay = () => {
const duration = Math.floor(this.audio.duration);
this.setState({
duration,
isCanPlay: true
});
}
};
componentDidMount() {
this.init();
}
componentWillReceiveProps(nextProps) {
if(nextProps.src !== this.props.src) {
this.init(nextProps);
}
}
componentWillUnmount() {
if(this.timer) {
clearTimeout(this.timer);
}
if(this.interval) {
clearInterval(this.interval);
}
if(this.audio) {
this.audio.currentTime = 0;
this.audio.pause();
}
}
render() {
const {
playStatus,
duration,
currentDuration
} = this.state;
const btn_img = playStatus ? icon_pause : icon_play;
const durationStr = this.getDurationString(duration);
const currentDurationStr = this.getDurationString(currentDuration);
return (
<div className="AudioItem" ref={audioItem => this.audioItem = audioItem}>
<div className="audio-item">
{/* 播放按钮 */}
<div className="audio-item-btn" onClick={this.handlePlayStatusChange}>
<img src={btn_img} alt="icon" />
</div>
<div className="audio-item-content">
<div className="audio-item-top">
{/* 播放中的动画 */}
<div className={`audio-item-bars ${playStatus ? 'animate' : ''}`}>
<div className="audio-item-bar"></div>
<div className="audio-item-bar"></div>
<div className="audio-item-bar"></div>
<div className="audio-item-bar"></div>
<div className="audio-item-bar"></div>
</div>
{/* 播放的时长 */}
<div className="audio-item-range">
<div className="audio-item-current">{currentDurationStr}</div>
<div className="audio-item-duration">{durationStr}</div>
</div>
</div>
<div className="audio-item-bottom">
<Slider
trackStyle={{
height: '0.13rem',
backgroundColor: '#10C0DC',
borderRadius: '0.07rem 0 0 0.07rem'
}}
railStyle={{
height: '0.13rem',
backgroundColor: '#DEEAEC',
borderRadius: '0 0.07rem 0.07rem 0'
}}
handleStyle={{
width: '0.32rem',
height: '0.32rem',
border: 'none',
marginTop: '-0.1rem',
borderRadius: '50%',
backgroundColor: '#FFFFFF',
boxShadow: '0.08rem 0.08rem 0.21rem rgba(140, 181, 187, 0.3), -0.08rem -0.08rem 0.21rem rgba(140, 181, 187, 0.3)'
}}
style={{ marginLeft: '0.34rem', marginRight: 0 }}
value={currentDuration}
min={0}
max={duration}
onChange={this.handleSilderChange}
/>
</div>
</div>
</div>
</div>
);
}
}
export default AudioItem;
.AudioItem {
display: flex;
justify-content: center;
width: 100%;
.audio-item {
display: flex;
justify-content: space-between;
align-items: center;
width: 9.09rem;
height: 1.87rem;
padding: 0 0.4rem;
background-color: #F6F8FA;
border-radius: 0.2rem;
&-btn {
width: 0.96rem;
height: 0.96rem;
img {
width: 100%;
height: 100%;
}
}
&-content {
width: calc(100% - 1.32rem);
}
&-top,
&-range {
display: flex;
align-items: center;
justify-content: space-between;
}
&-top {
margin-bottom: 0.26rem;
}
&-bars {
display: flex;
align-items: center;
width: 0.48rem;
height: 0.48rem;
overflow: hidden;
&.animate {
.audio-item-bar {
&:nth-child(1),
&:nth-child(5) {
animation: playAudio1 0.8s infinite ease-in;
}
&:nth-child(2),
&:nth-child(4) {
animation: playAudio2 0.8s infinite ease-in-out;
}
&:nth-child(3) {
animation: playAudio3 0.8s infinite ease-out;
}
}
@keyframes playAudio1 {
0%, 100% {
height: 0.2rem;
}
50% {
height: 0.48rem;
}
}
@keyframes playAudio2 {
0%, 50%, 100% {
height: 0.3rem;
}
25% {
height: 0.48rem;
}
75% {
height: 0.2rem;
}
}
@keyframes playAudio3 {
0%, 100% {
height: 0.48rem;
}
50% {
height: 0.2rem;
}
}
}
}
&-range {
width: calc(100% - 0.88rem);
}
&-bottom {
width: 100%;
height: 0.13rem;
}
&-bar {
flex-shrink: 0;
flex-grow: 0;
width: 0.4rem;
height: 0.48rem;
transform-origin: left center;
transform: scaleX(0.1);
border-radius: 0.1rem;
background-color: #10C0DC;
&:nth-child(1),
&:nth-child(5) {
height: 0.2rem;
}
&:nth-child(2),
&:nth-child(4) {
height: 0.3rem;
}
&:not(:first-child) {
margin-left: -0.3rem;
}
}
}
}
発生した問題
- オーディオにcurrentTimeプロパティを設定できない場合があります。これは、サーバーの応答ヘッダーのキャッシュコントロールに問題があるためです。応答ヘッダーを変更してください。
- 一部のオーディオでは、オーディオ側で再生時間を取得できない場合があります。たとえば、iOS側では再生前に再生時間を取得できないようです。再生後にのみ取得できると推定されています。この場合、バックエンドが直接オーディオ期間を返すことをお勧めします。
- スライドして進行状況を変更する場合、AliのUIコンポーネントSliderを使用しているため、実際の効果はあまり良くありません。時間があれば、自分で作成できます。
- 再生するオーディオが大きすぎる場合は、リソースをプリロードすることをお勧めします。
- 実際、考慮されていない場所はまだたくさんありますが、単純な再生であればそれで十分です。参照されているサードパーティコンポーネントは他のコンポーネントのスタイルを変更したくないのです。自分で作成する場合は、さらに高くカスタマイズできます。
スタイルのプレビュー