前言:自己學(xué)習(xí)VUEJS也一段時間,但一直沒有做出來一東西励背。我自己一直喜歡用網(wǎng)易云音樂app乾蛤,于是乎就做了這個app。
技術(shù)棧
- vue全家桶 (vue vue-router vuex)
- axios
- Muse-UI(一個基于Vue2.x的material design 風(fēng)格UI框架)
功能與思路分析
我之前學(xué)習(xí)JS的時候?qū)tml5 audio研究過捷泞,也寫過一些例子,那時的功能并不是很全面寿谴。在寫這個程序之前锁右,我好好的查閱了當前的HTML5中的audio標簽,發(fā)現(xiàn)園子上一位園友總結(jié)的很不錯(這里)讶泰。于是就先把網(wǎng)易云音樂最基本的功能實現(xiàn)咏瑟,歌單部分(這也是我喜歡網(wǎng)易云音樂的原因這一),然后實現(xiàn)音樂的上一曲痪署、下一曲码泞,播放、暫停狼犯。列表功能余寥。
后臺
后臺采用.net做為后臺提供系統(tǒng)請求所用的API(源碼),原理很簡單就是用.net偽裝成一個客戶端去訪問網(wǎng)易云音樂的API然后悯森,把返回的json數(shù)據(jù)轉(zhuǎn)發(fā)出來宋舷。同時服務(wù)端做下跨域處理。
核心代碼:
/// <summary>
/// 請求網(wǎng)易云音樂接口
/// </summary>
/// <typeparam name="T">要請求的接口類型</typeparam>
/// <param name="config">要請求的接口類型的對象</param>
/// <returns>請求結(jié)果(JSON)</returns>
public static string Request<T>(T config) where T : RequestData, new()
{
// 請求URL
string requestURL = config.Url;
// 將數(shù)據(jù)包對象轉(zhuǎn)換成QueryString形式的字符串
string @params = config.FormData.ParseQueryString();
bool isPost = config.Method.Equals("post", StringComparison.CurrentCultureIgnoreCase);
if (!isPost)
{
// get方式 拼接請求url
string sep = requestURL.Contains('?') ? "&" : "?";
requestURL += sep + @params;
}
HttpWebRequest req = (HttpWebRequest)WebRequest.Create(requestURL);
req.Accept = "*/*";
req.Headers.Add("Accept-Language", "zh-CN,zh;q=0.8,gl;q=0.6,zh-TW;q=0.4");
// 如果服務(wù)端啟用了GZIP瓢姻,那么下面必須解壓祝蝠,否則一直亂碼。
// 參見:http://www.crifan.com/set_accept_encoding_header_to_gzip_deflate_return_messy_code/
req.Headers.Add("Accept-Encoding", "gzip,deflate,sdch");
req.ContentType = "application/x-www-form-urlencoded";
req.KeepAlive = true;
req.Host = "music.163.com";
req.Referer = "http://music.163.com/search/";
req.UserAgent = "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_9_2) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/33.0.1750.152 Safari/537";
// 設(shè)置cookies
req.Headers["Cookie"] = "appver=1.5.2";
req.Method = config.Method;
req.AutomaticDecompression = DecompressionMethods.GZip;
if (isPost)
{
// 寫入post請求包
byte[] formData = Encoding.UTF8.GetBytes(@params);
// 設(shè)置HTTP請求頭 參考:https://github.com/darknessomi/musicbox/blob/master/NEMbox/api.py
req.GetRequestStream().Write(formData, 0, formData.Length);
}
// 發(fā)送http請求 并讀取響應(yīng)內(nèi)容返回
return new StreamReader(req.GetResponse().GetResponseStream(), Encoding.GetEncoding("UTF-8")).ReadToEnd();
}
vuejs部分
項目結(jié)構(gòu)
├── index.html
├── main.js
├── api
│ └── ... # 抽取出API請求
├── components
│ ├── playBar.vue
│ └── ...
└── store
│ └── index.js # 整個項目的vuex部分
└── router
│ └── router.js # 整個項目的路由
└── utils # 一些工具類模塊
│
└── views # 項目中的一些route-view
說項目的路由之前幻碱,先來看一張效果圖
對于整個項目來說:視圖區(qū)別在于頂部導(dǎo)航绎狭,下面的bar的是否出來取決于,當前系統(tǒng)列表中是否有歌曲褥傍,如果有就會出現(xiàn)儡嘶。
router.js核心部分
const router = new VueRouter({
mode: 'history',
routes: [{
path: '/index',
component: require('../views/index'),
children: [
{
path: 'rage',
component: require('../views/rage')
},
{
path: 'songList',
component: require('../views/songList')
},
{
path: 'leaderBoard',
component: require('../views/leaderBoard')
},
{
path: 'hotSinger',
component: require('../views/hotSinger')
}
]
}, {
name: 'playerDetail',
path: '/playerDetail/:id',
component: require('../views/playerDetail')
}, {
path: '/playListDetail/:id',
name: 'playListDetail',
component: require('../views/playListDetail')
}, {
path: '*', redirect: '/index/rage'
}],
// 讓每個頁面都滾動到頂部,改變模式為mode: history
scrollBehavior (to, from, savedPosition) {
if (savedPosition) {
return savedPosition
} else {
return { x: 0, y: 0 }
}
}
})
vuex部分
這部分摔桦,主要是歌曲這一塊社付,因為不同的頁面有不同的使用到了歌曲信息,把把這部分數(shù)據(jù)放到vuex中做統(tǒng)一的數(shù)據(jù)處理邻耕!
sotre/index.js
const store = new Vuex.Store({
state: {
audio: {
'id': 0,
'name': '歌曲名稱',
'singer': '演唱者',
'albumPic': '/static/player-bar.png',
'location': '',
'album': ''
},
lyric: '正在加載中鸥咖。。',
currentIndex: 0, // 當前播放的歌曲位置
playing: false, // 是否正在播放
loading: false, // 是否正在加載中
showDetail: false,
songList: [], // 播放列表
currentTime: 0,
tmpCurrentTime: 0,
durationTime: 0,
bufferedTime: 0,
change: false // 判斷是更改的時間還是播放的時間
},
getters: {
audio: state => state.audio,
playing: state => state.playing,
loading: state => state.loading,
showDetail: state => state.showDetail,
durationTime: state => state.durationTime,
currentIndex: state => state.currentIndex,
bufferedTime: state => state.bufferedTime,
tmpCurrentTime: state => state.tmpCurrentTime,
songList: state => state.songList,
change: state => state.change,
currentTime: state => state.currentTime,
prCurrentTime: state => {
return state.currentTime / state.durationTime * 100
},
prBufferedTime: state => {
return state.bufferedTime / state.durationTime * 100
}
},
mutations: {
play (state) {
state.playing = true
},
pause (state) {
state.playing = false
},
toggleDetail (state) {
state.showDetail = !state.showDetail
},
setAudio (state) {
state.audio = state.songList[state.currentIndex - 1]
},
setAudioIndex (state, index) {
state.audio = state.songList[index]
state.currentIndex = index + 1
},
removeAudio (state, index) {
state.songList.splice(index, 1)
state.audio = state.songList[index - 1]
state.currentIndex = state.currentIndex - 1
if (state.songList.length === 0) {
state.audio = {
'id': 0,
'name': '歌曲名稱',
'singer': '演唱者',
'albumPic': '/static/player-bar.png',
'location': '',
'album': ''
}
state.playing = false
}
},
setChange (state, flag) {
state.change = flag
},
setLocation (state, location) {
state.audio.location = location
},
updateCurrentTime (state, time) {
state.currentTime = time
},
updateDurationTime (state, time) {
state.durationTime = time
},
updateBufferedTime (state, time) {
state.bufferedTime = time
},
changeTime (state, time) {
state.tmpCurrentTime = time
},
openLoading (state) {
state.loading = true
},
closeLoading (state) {
state.loading = false
},
resetAudio (state) {
state.currentTime = 0
},
playNext (state) { // 播放下一曲
state.currentIndex++
if (state.currentIndex > state.songList.length) {
state.currentIndex = 1
}
state.audio = state.songList[state.currentIndex - 1]
},
playPrev (state) { // 播放上一曲
state.currentIndex--
if (state.currentIndex < 1) {
state.currentIndex = state.songList.length
}
state.audio = state.songList[state.currentIndex - 1]
},
addToList (state, item) {
var flag = false
state.songList.forEach(function (element, index) { // 檢測歌曲重復(fù)
if (element.id === item.id) {
flag = true
state.currentIndex = index + 1
}
})
if (!flag) {
state.songList.push(item)
state.currentIndex = state.songList.length
}
},
setLrc (state, lrc) {
state.lyric = lrc
}
},
// 異步的數(shù)據(jù)操作
actions: {
getSong ({commit, state}, id) {
commit('openLoading')
Axios.get(api.getSong(id)).then(res => {
// 統(tǒng)一數(shù)據(jù)模型兄世,方便后臺接口的改變
var url = res.data.data[0].url
commit('setAudio')
commit('setLocation', url)
})
},
getLrc ({commit, state}, id) {
commit('setLrc', '[txt](加載中啼辣。。御滩。')
Axios.get(api.getLrc(id)).then(res => {
// 1鸥拧、先判斷是否有歌詞
if (res.data.nolyric) {
commit('setLrc', '[txt](⊙0⊙) 暫無歌詞')
} else {
console.log(res.data.lrc.lyric)
commit('setLrc', res.data.lrc.lyric)
}
})
}
}
})
最后上點項目截圖
github項目地址:https://github.com/javaSwing/NeteaseCloudWebApp
目前只完成app歌單部分党远,也是最核心的部分。這個項目會一直更新!如果覺的不錯就給個star吧