雖然全網上 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 //下載mmPlayercd 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移動端版本(高仿網易雲音樂 自我感覺這高仿沒毛病
最後我們切入主題,歡迎小哥哥、小姐姐們給我點 “Star” “Fork”(地址在這裡 Vue-mmPlayer 原始碼地址),畢竟第一次發文騙贊(微微一笑),小哥哥、小姐姐們給點鼓勵。
如有問題請在本文回覆或者直接在 Issues 中提,或者您發現問題並有非常好的解決方案,歡迎 PR
來源:https://juejin.im/post/5afcdc73518825428630b216?utm_medium=fe&utm_source=weixinqun