基于React的音乐播放器【仿网易云音乐web】

3,026 阅读9分钟

写在前面

1、基于create-react-app脚手架创建,仿网易云音乐web端的一个音乐播放器

2、现在处于1.0版本,没有涉及到api访问,全部都是静态资源,包括音乐资源

3、因为暂时缺失api支持,歌词跟随播放滚动功能暂时缺失,相关收藏分享等业务功能缺失

4、实现了80%以上的播放器自身功能,业务性的功能,没有api,确实没办法实现

5、因为是静态音乐资源,浏览器缓存需要10-20s左右的时间,后期基于整体项目我会换成第三方api的

6、本项目包含音乐播放器主体和一个歌单功能

7、点这里是项目DEMO

8、点这里是GitHub地址,如果对你有帮助,可以给我个赞,是对我的最大鼓励

项目整体截图

react-music-component整体截图.png

播放器入参说明

传入参数 参数类型 参数详细说明 是否必须
musicList Array id:唯一标识 ,title:歌曲名称 ,info:歌曲作者信息 ,resource:资源地址 ,time:歌曲持续时长(用于歌单中歌曲信息展示,播放器) ,img:歌曲缩略图地址
onDeleteMusic Function 删除当前歌单指定歌曲方法
onDeleteAllMusic Function 删除当前歌单全部歌曲方法

播放器实现功能

1、歌曲播放相关功能,包括:

(1)歌曲播放、暂停

(2)进度条点击,拖拽

(3)音量调整

(4)上一首歌,下一首歌,切歌功能

(5)播放模式切换(单曲循环,歌单顺序,歌单随机

2、歌单功能,包括

(1)列表展示歌单

(2)删除全部歌单歌曲,删除歌单中某一首歌曲

核心技术点

1、H5标签audio的相关api使用

2、react基本生命周期使用,组件化思想

3、播放进度条配合audio的css绘制思路

技术点拆分

  • audio的使用

1、react中通过ref可以控制audio,使用了三个监听方法,分别是:"canplay","timeupdate","ended",

(1)canplay:audio准备就绪,可以开始播放音乐的时候。用于获取当前播放音乐的总时长

(2)timeupdate:音乐播放时间时间变化监听,用于获取音乐播放当前缓存进度和播放进度,同时计算比例计算播放进度条推进进度

(3)ended:当前音乐播放结束监听,用于自动切歌

2、使用到的audio相关方法:

(1)audio.buffered:获取当前audio的缓存对象,获取缓存时间的方法是:buffered.end(buffered.length - 1);

(2)audio.duration:获取当前音乐总时长

(3)audio.currentTime:当前播放中的音乐,当前播放到多少时刻(可以直接设置currentTime,audio会自动跳转到设置的时刻)

(4)audio.play():音乐播放方法

(5)audio.pause():音乐暂停方法

(6)audio.volume:当前音乐音量设置(范围:0-1)

  • react生命周期中,判断歌单变化

1、static getDerivedStateFromProps(nextProps, prevState)方法

歌单变化(在播放中删除了当前播放的歌曲,或者删除了其他歌曲,都是属于歌单变化的情况),需要判断新传入的歌单和之前歌单的内容对比,如果删除的音乐是当前播放的音乐,则切歌至下一首,如果删除的歌曲不是当前播放歌曲,则当前播放音乐继续正常播放,歌单变化即可

2、componentDidUpdate(prevProps, prevState, snapshot)方法

判断当前播放音乐变了,则需要重置播放进度条等相关UI

3、componentDidMount() 方法

添加audio的相关监听方法,初始化缓存条,进度条,音量等相关UI

  • 相关进度条绘制(缓存进度条、播放进度条、音量控制进度条)

1、缓存进度条

在audio的timeupdate监听方法中,通过audio.buffered获取到audio的缓存对象,通过当前缓存时间比例计算缓存进度条的宽度,同样是通过ref获取到缓存进度条div的DOM,直接通过计算出来的宽度设置缓存进度条的宽度,这里需要注意的一点就是缓存进度条和播放进度条是重合放置的,但是要避免缓存进度条把播放进度条给遮挡了,播放进度条的视图层应该是最高的,所以这里要使用z-index属性确保播放进度条在最上面。

2、播放进度条

控制原理和缓存进度条类似,只是播放进度条会在很多地方触发变化,而且有点击,拖拽等事件

(1)点击事件 通过onClick事件触发,通过e.pageX -this.processPlayed.getBoundingClientRect().left;,e.pageX可以获取到当前点击处距离DOM文档左侧x轴的距离,减去当前进度条左边框距离左侧x轴的距离,就能得到当前点击处相对进度条的偏移距离,这样计算对应比例,同时计算设置audio.currentTime,就能实现点击进度条控制音乐播放进度的效果

(2)拖拽

其实所有拖拽第三方组件的实现,都是类似的原理。 拖拽分为几个步骤,第一步:鼠标按下,第二步:鼠标移动,第三步:鼠标松起。这里其实就是分别对应了三个时间,onMouseDown,onMouseMove,onMouseUp,方法内容和(1)的点击事件的类似,其实就是计算当前e(鼠标事件触发点)的位置,然后通过ref拿到进度条的小圆点设置style.left,拿到进度条的style.width设置进度条的宽度就可以了,但是拖拽有两个注意点需要注意: 【注意点1】拖拽配合计算拖拽的比例的时候,需要在onMouseUp事件中设置audio的currentTime,这是音乐如果在onMouseMove及时设置了音乐的进度,会导致音乐听起来有快进和快退的感觉,这样体验感会很差,正确的做法就是在松开鼠标的时候,设置音乐进度,及时设置的只有进度条的进度 【注意点2】onMouseUp会监听到任何情况下的鼠标抬起事件,如果是在播放器任意非点击处点击了一下,也会触发到onMouseUp,这里我先解释一下这个问题的引发原因,正常的拖拽,我们的鼠标难免会有可能偏移到进度条以外,如下图,红色示意就是我们可能会产生的鼠标的偏移,也有可能会偏下,因为进度条的长宽都是确定的,并没有覆盖到可能会产生的偏移区域,所以如果把所有拖拽时间都放到进度条上,如果鼠标产生了这种偏移,就会发生失去响应的情况,我们可以去看看其他播放器,对于这种偏移的支持都是有的,在实际应用中,这种允许内的鼠标拖拽偏移,也是允许有的,所以我们的方法必须考虑到这点,所以我的进度条上小圆点只有onMouseDown一个方法,同时在最外层整个播放器div上放了onMouseMove和onMouseUp两个事件,就是为了兼容这种拖拽偏移的问题,onMouseMove可以直接在整个播放器范围内监听鼠标移动,这里需要判断,拖拽移动如果超出了进度条最左边,就将进度设置为0,超过最右边,就将进度设置为最大宽度,而进度条上这个onMouseDown唯一的监听方法就是表示拖拽的开始,一定是从进度条最前端的小圆点开始的,这个方法中会设置一个state,标识拖拽开始,然后onMouseMove事件中会及时计算进度条宽度,最后onMouseUp会设置拖拽标识结束,同时设置进度条宽度和音乐播放进度,这里就回到我们【注意点2一开始说的问题,如果在onMouseDown不设置这个拖拽开始的标识的话,onMouseMove和onMouseUp就不知道是为了拖拽而监听的方法,所以用户在播放器内任意位置按下鼠标开始拖拽,和松开鼠标的时候,都会进行进度条位置的计算,我们本来是想兼容拖拽偏移,才把onMouseMove和onMouseUp放到播放器最外层的,这样反而导致了bug,解决的办法就是让这两个方法知道,我是在拖拽中,才进行进度计算,而拖拽的开始,一定是在进度条的小圆点上的,所以将onMouseDown放在小圆点上的,设置state,表示拖拽开始,这样就完美解决了】

拖拽鼠标偏移示例图.png

3、音量进度条

音量进度条的绘制类似播放进度条,这里不做多的赘述,需要注意的地方有两点:

(1)音乐播放条是纵向的,计算拖拽需要用pageY和getBoundingClientRect().top

(2)音乐播放条div需要注意z-index,因为会和歌单弹出画面重合,需要保证音乐控制条在歌单的上面

  • 歌单的实现

歌单其实没有什么复杂的逻辑相关,主要是展示性的东西,因为歌单多是业务性的逻辑,业务性的逻辑,没有api配合,确实是暂时无法实现的,比如分享,收藏这些功能。所以笔者实现的功能就只有删除歌单全部歌曲和删除某一首音乐的功能,当然,也是静态功能,并不会真正从服务器上删除,删除逻辑很简单,就是处理歌单数组的事情,重点是播放器组件对于传入的新歌单的处理,我在文章上面的“react生命周期中,判断歌单变化”里面已经写了的,这里就不重复了。

写在最后

本文着重是对于技术点和思路的解析,关于audio更多的方法使用,读者可以自行去官方api查看并研究(audio的api还多着呢),另外关于播放器的实现,主色调是仿的网易云音乐web端的,但是很多UI网易云音乐都是用的background属性,笔者只好自己在阿里开源图标库里面尽量找一些风格比较统一的图标来使用,关于整体布局的实现,有参考网易云音乐web端,但是并没有照抄(吐槽:笔者是真的不喜欢一个行级块内,所有元素都用float:left来实现行级排版这种写法),虽然笔者的写法肯定不是最好的,读者自行借鉴就好。

最后的最后,如果对您有帮助,在GitHub和博客点个赞,就是对我的最大支持了,谢谢!