还是直接上代码
/** * 音频播放组件 */ 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属性,这个是由于服务端的响应头中的cache-control有问题,改一下响应头就可以了。 针对有些音频无法在audio这边获取到duration的,比如ios端好像在播放之前拿不到duration,估计是要等播放之后才能去获取,还没尝试,主要是没ios设备,不好测试。这种情况建议是后端把音频的duration直接返回过来。 滑动改变进度的时候,由于用的时候阿里的UI组件Slider,实际效果不是很好,有时间的可以自己写一个。 针对于需要播放的音频如果过大的,最好做好预加载资源。 其实还有很多地方没有考虑到,不过如果仅仅只是简单播放就足够了,引用的第三方组件实在是不喜欢改别人的样式,自己写的话,可定制化更高。样式预览
查看更多关于React自定义Audio播放组件的详细内容...
声明:本文来自网络,不代表【好得很程序员自学网】立场,转载请注明出处:http://haodehen.cn/did222610