虽然全网上 Vue 仿饿了么、xx音乐的项目一大堆,但是我还是厚着脸皮来了,毕竟我也稍微标新立异,PC端为主,移动端为辅打造的 web 音乐播放器,所以说大佬们关爱下,毕竟我这个播放器刚从韩国回来!!!
模仿QQ音乐网页版界面,采用flexbox和position布局; mmPlayer虽然是响应式,但主要以为PC端为主,移动端只做相应适配(未做歌词显示); 只做主流浏览器兼容(对IE说拜拜并特别优化,想想以前做项目还要兼容IE7,都是泪啊!!!)
api:一个开源的网易云音乐 NodeJS 版 API(有 api 才有动力写!!!)
在线演示地址 服务器脆弱,小哥哥小姐姐要温柔对待哦(最好是 PC 浏览哦)
桌面版下载 有点简陋,别介意,主要人懒还有就是没空
如何安装与使用
mmPlayer
git clone https://github.com/maomao1996/Vue-mmPlayer.git //下载mmPlayer
cd mmPlayer //进入mmPlayer播放器目录
npm install //安装依赖
npm run dev //服务端运行
npm run build //项目打包
复制代码
后台服务器
cd mmPlayer/server //进入后台服务器目录
npm install //安装依赖
node app.js //服务端运行 访问 http://localhost:3000
复制代码
运行mmPlayer后无法获取音乐请检查后台服务器是否启动
api目录下music的url地址要和后台服务器地址一致
技术栈
- Vue-Cli(Vue 脚手架工具)
- Vue(核心框架)
- Vue-Router(页面路由)
- Vuex(状态管理)
- ES 6 / 7 (JavaScript 语言的下一代标准)
- Less(CSS预处理器)
- Axios(网络请求)
- FastClick(解决移动端300ms点击延迟)
界面欣赏
PC端界面自我感觉还行, 就是移动端界面总觉得怪怪的,奈何审美有限,所以又去整了高仿网易云的 React 版本(如果小哥哥、小姐姐们有好看的界面,欢迎交流哈)
PC
正在播放
排行榜
搜索
我的歌单
我听过的
歌曲评论
移动端
功能
播放器、快捷键操作、歌词滚动、正在播放、排行榜、歌单详情、搜索、播放历史、查看评论、同步网易云歌单
播放器(核心)
播放器功能其实也就那样,调用 HTML5 音频的方法、属性和事件,网上各种文章也都介绍烂了,但是骗赞要有诚意
我实现的功能有这些:上一曲、下一曲、暂停、播放、单曲循环、列表循环、随机播放、顺序播放、进度条、音量控制
在介绍这些功能之前先理理思路,这里用个小 demo 介绍,毕竟没有代码,虾扯蛋谁不会啊,先上代码
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Audio</title>
<script src="https://cdn.jsdelivr.net/npm/vue@2.5.16/dist/vue.js"></script>
</head>
<body>
<div id="Audio">
<audio ref="mmAudio" :src="src" controls></audio>
<button @click="prev">上一曲</button>
<button @click="play">暂停/播放</button>
<button @click="next">下一曲</button>
</div>
<script>
const vm = new Vue({
el: '#Audio',
data: {
list: [
'https://music.163.com/song/media/outer/url?id=450424527.mp3',
'https://music.163.com/song/media/outer/url?id=557581284.mp3',
'https://music.163.com/song/media/outer/url?id=452986458.mp3'
],//歌曲数组
index: 0,//当前歌曲下标
},
computed: {
src() {
return this.list[this.index] // 当前播放歌曲
}
},
methods: {
prev() {//上一曲
let index = this.index - 1;
if (index < 0) {
index = this.list.length - 1
}
this.index = index;
this.$nextTick(() => this.$refs.mmAudio.play())
},
play() {//暂停/播放
this.$nextTick(() => this.$refs.mmAudio.paused ? this.$refs.mmAudio.play() : this.$refs.mmAudio.pause())
},
next() {//下一曲
let index = this.index + 1;
if (index === this.list.length) {
index = 0
}
this.index = index;
this.$nextTick(() => this.$refs.mmAudio.play())
}
}
})
</script>
</body>
</html>
复制代码
这段代码的逻辑非常简单,我们会添加一个 computed
动态生成 歌曲 src
,当点击暂停/播放的时候,会调用 play
方法,修改 播放状态;当点击上一曲或者下一曲的时候,会修改当前歌曲播放的 index
,然后会触发 computed
修改 src
然后调用 play
方法播放音乐
看完这个小 demo 这些功能都好理解了
上一曲/下一曲:修改当前歌曲播放的 index
,然后会触发 computed
修改 src
,然后调用 play
方法播放音乐
暂停/播放:通过 Audio 的 paused
属性判断音频是否处于暂停状态,如果返回 true 调用 play
播放音乐,如果返回 false 调用 paused
暂停音乐
单曲循环:调用 Audio 的 ended
事件,在当前歌曲播放结束后将 currentTime
属性重置为 0
列表循环:调用 Audio 的 ended
事件,在其回调中调用下一曲方法
随机播放:通过一个方法打乱歌曲数组,打乱数组前先用一个数组存放原始歌曲数组
顺序播放:调用 Audio 的 ended
事件,判断当前歌曲下标是否和歌曲数组长度 -1 相等,如果相等就不再调用下一曲方法
进度条/音量控制:使用自己封装的 progress 组件 来进行拖动,点击操作来修改对应的播放进度 currentTime
和音量 volume
小记:在 progress
的事件绑定中只有鼠标按下mousedown
和触摸开始事件 touchstart
是绑定在对应的 DOM 节点上,其他鼠标移动mousemove
和触摸移动touchmove
、鼠标释放mouseup
和触摸释放touchend
等事件绑定的 DOM 都是 document
,不然会出现各种掉链子的问题,比如拖动过程中不小心焦点不在对应的 DOM 上了就会中断拖动
快捷键操作
上一曲 Ctrl + Left
播放暂停 Ctrl + Space
下一曲 Ctrl + Right
切换播放模式 Ctrl + O
音量加 Ctrl + Up
音量减 Ctrl + Down
歌词滚动
歌词滚动原理:根据当前音乐的播放时间 audio.currentTime
去匹配歌词 JSON 数据的时间,然后匹配后的歌词居中显示并高亮
先来张歌词 JSON 的迷人玉照给各位小哥哥、小姐姐们解解馋
{
lyric: "[by:鱼丸啊鱼丸QAQ] [00:00.00] 作曲 : willen [00:01.00] 作词 : 口袋易百 [00:04.60]伴唱:willen [00:05.10]混音/母带:willen [00:55.37]外婆的话 还记得吗 [00:59.01]慈祥的笑容伴我长大 [01:02.75]每当庭院开满了桂花 [01:06.50]淡淡花香都是爱的牵挂 [01:10.30]外婆的话 还记得吗 [01:14.01]受伤的孩子别忘了回家 [01:17.66]夕阳西下 岁月染白了发 "
}
复制代码
由于 audio.currentTime
是以秒计,而歌词 JSON 的格式是酱样子的 [00:00.00]
所以我们要先把歌词 JSON 化个妆
// 这是化妆过程,具体流程我就不多说了,毕竟我是厚着脸皮来掘金骗小心心的
function parseLyric(lrc) {
let lyrics = lrc.split("\n");
let lrcObj = [];
for (let i = 0; i < lyrics.length; i++) {
let lyric = decodeURIComponent(lyrics[i]);
let timeReg = /\[\d*:\d*((\.|\:)\d*)*\]/g;
let timeRegExpArr = lyric.match(timeReg);
if (!timeRegExpArr) continue;
let clause = lyric.replace(timeReg, '');
for (let k = 0, h = timeRegExpArr.length; k < h; k++) {
let t = timeRegExpArr[k];
let min = Number(String(t.match(/\[\d*/i)).slice(1)),
sec = Number(String(t.match(/\:\d*/i)).slice(1));
let time = min * 60 + sec;
if (clause !== '') {
lrcObj.push({time: time, text: clause})
}
}
}
return lrcObj;
}
复制代码
我比较菜,火星文(正则)全靠搜索引擎,这个是歌词正则原地址,不过我的稍加修饰了下
当这一切 OK 后就只要匹配时间居中并高亮展示当前歌词就行啦! 目前我是通过 for 循环对比大小找到第一个比当前播放时间大的歌词时间,但是我一直觉得这样写不够优雅,无奈又想不到其他方法,希望知道优雅方法的小哥哥、小姐姐来指点迷津
正在播放
显示和管理当前播放的歌单,可以清空当前播放器列表、删除置顶歌曲,修改歌曲的播放状态
排行榜
调用对应 API 接口获取网易云音乐的排行榜列表(目前没做图片懒加载)
歌单详情
传入歌单 ID 调用对应 API 接口获取当前歌单下所有歌曲,由于是获取所有歌曲在移动端滑动时会有所卡顿,这个后期会加入Better-Scroll(一款重点解决移动端各种滚动场景需求的插件)
搜索
目前只实现了歌曲的搜索,后续会完善 专辑 / 歌手 / 歌单 / 用户 的搜索 通过搜索关键字请求 API 获取搜索数据并显示歌曲 分页:调用 scroll 事件,滚动到底部下载下一页,目前是50条每页,当所有数据请求完毕后会提示:没有更多歌曲啦!
在上次网易云音乐和 QQ 音乐版权之争中,周杰伦的所有单曲全部 GG ,然后我就在播放事件中先去请求当前歌曲的 url ,如果没有就会提示:当前歌曲无法播放;如果不做这个,万一又被怼那就不好了,毕竟用户是大佬,惹不起也躲不起
播放历史
调用 canplay
事件,将不会播放出错的歌曲通过 localStorage
存储 PS:一开始我是通过 play
事件 ,结果不管你能不能放都会加入播放历史,然后被吐槽了,最后各种研究发现 canplay
更优雅
查看评论
这个是段子云音乐的一大亮点,必须要加上 通过当前播放音乐的 id
调用对应的 API 接口 分别展示热门评论和最新评论 当时做这个的时候界面简直小意思,但是评论时间简直郁闷,有:
刚刚 / XX 分钟前 / XX:XX / XX 月 XX 日 / XX 年 XX 月 XX 日
大概花了半个多小时才 KO 掉,不过不知道为啥一直觉得我这么写不够优雅,希望知道优雅方法的小哥哥、小姐姐来指点迷津
同步网易云歌单
先去 网易云音乐 获取自己的 UID
然后通过调用对应的 API 接口 获取该用户的歌单,然后传入歌单 ID 获取歌单详情。
当对应的 UID
返回的 playlist
数组长度为 0 时提示 未查询找UID为 ${uid} 的用户信息
登陆成功后调用 localStorage
存储当前 UID
,下次打开时会先读取本地存储的 UID 进行登录操作
退出登录时清空 localStorage
,以免下次打开时还会登录
后续
- 代码提高复用率
- 优化移动端界面和体验,比如 Better-Scroll,图片懒加载
- 专辑 / 歌手 / 歌单 / 用户 的搜索
- 用 koa 重构后台服务
- 完善下桌面版的体验(毕竟太简陋,自己都看不下去了)
- ...(脑容量不够,暂时就想到这些)
感谢
其他
个人练手项目,主要有段时间闲得无聊,然后在逛 Gayhub
时发现个开源的音乐API —— 网易云音乐 NodeJS 版 API 最后就一边无耻的水群一边找好看的界面顺带整理下开发思路
曾经有段时间很多人问我用的什么什么 UI 框架,所以我另外再提一下,该项目基础 UI 全是个人结合项目和各大 UI 框架代码风格慢慢敲的,再推荐个 Vue 课程 —— Vue.js 音乐 App 高级实战课程
还有就是有木有 React 大佬带我,最近正在自学中,有很多迷津求解答 这是一边学一边做的项目 React移动端版本(高仿网易云音乐 自我感觉这高仿没毛病