文档章节

用VUEJS做一个网易云音乐(基于2.x)

JavaSwing
 JavaSwing
发布于 2017/02/16 16:02
字数 1287
阅读 6044
收藏 307

前言:自己学习VUEJS也一段时间,但一直没有做出来一东西。我自己一直喜欢用网易云音乐app,于是乎就做了这个app。

技术栈

  • vue全家桶 (vue vue-router vuex)
  • axios
  • Muse-UI(一个基于Vue2.x的material design 风格UI框架)

功能与思路分析

我之前学习JS的时候对Html5 audio研究过,也写过一些例子,那时的功能并不是很全面。在写这个程序之前,我好好的查阅了当前的HTML5中的audio标签,发现博客园上一位园友总结的很不错(这里)。于是就先把网易云音乐最基本的功能实现,歌单部分(这也是我喜欢网易云音乐的原因之一),然后目前实现了音乐的上一曲、下一曲,播放、暂停。列表功能。

后台

后台采用.net做为后台提供系统请求所用的API(源码),原理很简单就是用.net伪装成一个客户端去访问网易云音乐的API然后,把返回的json数据转发出来。同时服务端做下跨域处理。

核心代码:

/// <summary>
/// 请求网易云音乐接口
/// </summary>
/// <typeparam name="T">要请求的接口类型</typeparam>
/// <param name="config">要请求的接口类型的对象</param>
/// <returns>请求结果(JSON)</returns>
public static string Request<T>(T config) where T : RequestData, new()
{
    // 请求URL
    string requestURL = config.Url;
    // 将数据包对象转换成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");
    // 如果服务端启用了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";            
    // 设置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);
        // 设置HTTP请求头  参考:https://github.com/darknessomi/musicbox/blob/master/NEMbox/api.py          
        req.GetRequestStream().Write(formData, 0, formData.Length);
    }            
    // 发送http请求 并读取响应内容返回
    return new StreamReader(req.GetResponse().GetResponseStream(), Encoding.GetEncoding("UTF-8")).ReadToEnd();
}

vuejs部分

项目结构

├── index.html
├── main.js
├── api
│   └── ... # 抽取出API请求
├── components
│   ├── playBar.vue
│   └── ...
└── store
│    └── index.js        # 整个项目的vuex部分
└── router
│   └── router.js        # 整个项目的路由
└── utils                # 一些工具类模块
│
└── views                # 项目中的一些route-view

说项目的路由之前,先来看一张效果图

对于整个项目来说:视图区别在于顶部导航,下面的bar的是否出来取决于,当前系统列表中是否有歌曲,如果有就会出现。

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部分

这部分,主要是歌曲这一块,因为不同的页面有不同的使用到了歌曲信息,把把这部分数据放到vuex中做统一的数据处理! 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) { // 检测歌曲重复
        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
    }
  },
  // 异步的数据操作
  actions: {
    getSong ({commit, state}, id) {
      commit('openLoading')
      Axios.get(api.getSong(id)).then(res => {
        // 统一数据模型,方便后台接口的改变
        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

码云地址:https://git.oschina.net/javaSwing/NeteaseCloudWebApp

目前只完成app歌单部分,也是最核心的部分。这个项目会一直更新!如果觉的不错就给个star吧

© 著作权归作者所有

JavaSwing
粉丝 21
博文 44
码字总数 8095
作品 0
杭州
程序员
私信 提问
加载中

评论(27)

JavaSwing
JavaSwing 博主

引用来自“忆山南南”的评论

想问一下 热门歌单是怎么抓取的呀
你可以看我另一个git项目,是基于.net写的抓去API
忆山南南
忆山南南
想问一下 热门歌单是怎么抓取的呀
JavaSwing
JavaSwing 博主

引用来自“be-quiet”的评论

弱弱的问个问题, 不同页面跳转, 数据不会重新再请求, 这是因为把所有页面的数据都放在 vuex 里吗?
不是,keep-alive标签
be-quiet
be-quiet
弱弱的问个问题, 不同页面跳转, 数据不会重新再请求, 这是因为把所有页面的数据都放在 vuex 里吗?
JavaSwing
JavaSwing 博主

引用来自“JavaSwing”的评论

引用来自“不只是个看客”的评论

其实一直有个问题,就是网易云那些API是怎么找的?求解答

抓包 以及 github

引用来自“不只是个看客”的评论

抓包是抓网页的还是PC客户端的?github怎么找呀。。。。
我抓的是PC,手机也是可以的!
不只是个看客

引用来自“JavaSwing”的评论

引用来自“不只是个看客”的评论

其实一直有个问题,就是网易云那些API是怎么找的?求解答

抓包 以及 github
抓包是抓网页的还是PC客户端的?github怎么找呀。。。。
JavaSwing
JavaSwing 博主

引用来自“不只是个看客”的评论

其实一直有个问题,就是网易云那些API是怎么找的?求解答

抓包 以及 github
不只是个看客
其实一直有个问题,就是网易云那些API是怎么找的?求解答
JavaSwing
JavaSwing 博主

引用来自“weiang12”的评论

很好奇你评论那一块怎么处理。我记得那一块加密处理。

github
JavaSwing
JavaSwing 博主

引用来自“每天多一点”的评论

vue比react好用吗?

没用过react,不过vue上手容易
基于 electron-vue 开发的音乐播放器

土狗音乐播放器 一个基于 electron-vue 开发的音乐播放器 (持续更新中..欢迎star) 源码地址:https://github.com/SmallRuralDog/electron-vue-music 运行 注意 修改 的api接口地址 网易云音乐...

小土狗
2018/07/24
0
0
Vue全家桶打造自适应 web 音乐播放器

虽然全网上 Vue 仿饿了么、xx音乐的项目一大堆,但是我还是厚着脸皮来了,毕竟我也稍微标新立异,PC端为主,移动端为辅打造的 web 音乐播放器,所以说大佬们关爱下,毕竟我这个播放器刚从韩国...

小小茂茂
2018/05/22
0
0
前端框架Vue.js——vue-i18n ,vue项目中如何实现国际化

一、前言 趁着10月的最后一天,来写一篇关于前端国际化的实践型博客。国际化应该都不陌生,就是一个网站、应用可以实现语言的切换。 在这就不谈原理,只说说如何实现中英文的切换。做技术的总...

飞翔的熊blabla
01/15
0
0
基于Electron-vue的音乐播放器 - electron-vue-music

土狗音乐播放器 一个基于 electron-vue 开发的音乐播放器 (持续更新中..欢迎star) 运行 git clone https://github.com/SmallRuralDog/electron-vue-music.git cd electron-vue-music npm in......

RuralDog
2018/07/22
7.2K
17
electron-vue关联知识点

前端框架Vue(11)——vue-cli 仿网易云音乐 Demo,环境搭建到开发 Vue 全家桶练手项目 https://segmentfault.com/a/1190000011275735 使用 electron-vue 构建你的桌面应用 http://blog.csdn...

egomy
2018/03/14
4
1

没有更多内容

加载失败,请刷新页面

加载更多

CSS3

一.复杂选择器 1.兄弟选择器 具备相同父级元素的平级元素之间称为兄弟元素 注意:兄弟选择器,只能往后,不能往前找 (1).相邻兄弟选择器,获取紧紧挨着某元素后面的兄弟元素 选择器1+选择器2...

wytao1995
10分钟前
1
0
Jmeter录制

1. 加HTTP(s) Test Script Recorder 2. 在 recorder下面加reocrding controller 3. 在HTTP(s) Test Script Recorder中设置下面几项 4. browser设置proxy, 注意端口要和step3中jmeter中的一致......

Rebecca_Hu
15分钟前
1
0
DIV+CSS忽悠前端小白

在大约两年前,DIV+CSS是一对很诱人的组合,会用DIV+CSS制作网页的人,常常会被人赞以大拇指的,记得06年初的时候,我用 div+css布局的一个纯静态网站还拿了学校网页设计比赛的一个奖。 今天...

前端老手
18分钟前
2
0
Win10子系统 linux(Ubuntu18.04) 安装Docker

1)原文件备份 sudo cp /etc/apt/sources.list /etc/apt/sources.list.bak 2)编辑源列表文件 sudo vim /etc/apt/sources.list 3)将原来的列表删除,添加如下内容(中科大镜像源) deb http...

jxldjsn
20分钟前
1
0
Ubuntu16.04安装Qt5.12.2

Ubuntu16.04安装Qt5.12.2 第一步:下载文件 https://download.qt.io/official_releases/qt/5.12/5.12.2/ 第二步:安装依赖库 sudo apt-get install build-essential sudo apt-get install li......

shzwork
26分钟前
4
0

没有更多内容

加载失败,请刷新页面

加载更多

返回顶部
顶部