雖然全網上 Vue 仿餓了麼、xx音樂的項目一大堆,可是我仍是厚着臉皮來了,畢竟我也稍微標新立異,PC端爲主,移動端爲輔打造的 web 音樂播放器,因此說大佬們關愛下,畢竟我這個播放器剛從韓國回來!!!css
模仿QQ音樂網頁版界面,採用flexbox和position佈局; mmPlayer雖然是響應式,但主要覺得PC端爲主,移動端只作相應適配(未作歌詞顯示); 只作主流瀏覽器兼容(對IE說拜拜並特別優化,想一想之前作項目還要兼容IE7,都是淚啊!!!)html
api:一個開源的網易雲音樂 NodeJS 版 API(有 api 纔有動力寫!!!)vue
Vue-mmPlayer 源碼地址html5
在線演示地址 服務器脆弱,小哥哥小姐姐要溫柔對待哦(最好是 PC 瀏覽哦)node
桌面版下載 有點簡陋,別介意,主要人懶還有就是沒空react
mmPlayerios
git clone https://github.com/maomao1996/Vue-mmPlayer.git //下載mmPlayer
cd mmPlayer //進入mmPlayer播放器目錄
npm install //安裝依賴
npm run dev //服務端運行
npm run build //項目打包
複製代碼
後臺服務器git
cd mmPlayer/server //進入後臺服務器目錄
npm install //安裝依賴
node app.js //服務端運行 訪問 http://localhost:3000
複製代碼
運行mmPlayer後沒法獲取音樂請檢查後臺服務器是否啓動github
api目錄下music的url地址要和後臺服務器地址一致web
PC端界面自我感受還行, 就是移動端界面總以爲怪怪的,奈何審美有限,因此又去整了高仿網易雲的 React 版本(若是小哥哥、小姐姐們有好看的界面,歡迎交流哈)
播放器、快捷鍵操做、歌詞滾動、正在播放、排行榜、歌單詳情、搜索、播放歷史、查看評論、同步網易雲歌單
播放器功能其實也就那樣,調用 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
,以避免下次打開時還會登陸
我的練手項目,主要有段時間閒得無聊,而後在逛 Gayhub
時發現個開源的音樂API —— 網易雲音樂 NodeJS 版 API 最後就一邊無恥的水羣一邊找好看的界面順帶整理下開發思路
曾經有段時間不少人問我用的什麼什麼 UI 框架,因此我另外再提一下,該項目基礎 UI 全是我的結合項目和各大 UI 框架代碼風格慢慢敲的,再推薦個 Vue 課程 —— Vue.js 音樂 App 高級實戰課程
還有就是有木有 React 大佬帶我,最近正在自學中,有不少迷津求解答 這是一邊學一邊作的項目 React移動端版本(高仿網易雲音樂 自我感受這高仿沒毛病
最後咱們切入主題,歡迎小哥哥、小姐姐們給我點 "Star" "Fork"(地址在這裏 Vue-mmPlayer 源碼地址),畢竟第一次發文騙贊(微微一笑),小哥哥、小姐姐們給點鼓勵。
若有問題請在本文回覆或者直接在 Issues 中提,或者您發現問題並有很是好的解決方案,歡迎 PR