Make a Netease Cloud Music with vue.js

Posted by geekette on Mon, 15 Jul 2019 02:33:52 +0200



Foreword: I have studied VUEJS for some time, but I haven't made anything. I have always liked to use Netease cloud music app, so I made this app.

technology stack

  • Vue family barrel (vue vue-router vuex)

  • axios

  • Muse-UI (a Vue2.x-based material design style UI framework)

Analysis of Function and Thought

When I studied JS, I was right. HTML5 audio studied and wrote some examples, but the functions were not very comprehensive at that time. Before writing this program, I checked the current one. Html5 In the audio label, I found that a gardener on the garden summed up very well.( Here ) So we first realized the basic function of Netease cloud music, the song list part (which is the reason why I like Netease cloud music), and then realized the last song, the next song, play, pause. List function.

Backstage

Background uses. net as the API for backstage system requests( Source code The principle is very simple. net disguises itself as a client to access the API of Netease Cloud Music and then forwards the returned json data. At the same time, the server does cross-domain processing.

Core code:

/// <summary>
/// Request Netease Cloud Music Interface
/// </summary>
/// <typeparam name="T">The type of interface to request</typeparam>
/// <param name="config">Objects of the interface type to request</param>
/// <returns>Request results(JSON)</returns>
public static string Request<T>(T config) where T : RequestData, new()
{
    // Request URL
    string requestURL = config.Url;
    // Converting Packet Objects into Strings in QueryString Form
    string @params = config.FormData.ParseQueryString();
    bool isPost = config.Method.Equals("post", StringComparison.CurrentCultureIgnoreCase);

    if (!isPost)
    {
        // get splicing request 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");
    // If GZIP is enabled on the server side, then the decompression must be done below, otherwise the code will be scrambled all the time.
    // See: 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";            
    // Setting cookies
    req.Headers["Cookie"] = "appver=1.5.2";
    req.Method = config.Method;
    req.AutomaticDecompression = DecompressionMethods.GZip;
    if (isPost)
    {
        // Write to post request package
        byte[] formData = Encoding.UTF8.GetBytes(@params);
        // Setting HTTP request header reference: https://github.com/darknessomi/music box/blob/master/NEMbox/api.py          
        req.GetRequestStream().Write(formData, 0, formData.Length);
    }            
    // Send an http request and read the response and return it
    return new StreamReader(req.GetResponse().GetResponseStream(), Encoding.GetEncoding("UTF-8")).ReadToEnd();
}

vuejs section

Project structure

├── index.html
├── main.js
├── api
│   └── ... # Extract API requests
├── components
│   ├── playBar.vue
│   └── ...
└── store
│    └── index.js        # vuex part of the whole project
└── router
│   └── router.js        # Routing of the whole project
└── utils                # Some Tool Class Modules
│
└── views                # Some route-view s in the project

Before you talk about the routing of a project, take a look at an effect map.

For the project as a whole, the view is different from the top navigation. Whether the bar below comes out depends on whether there are songs in the current system list, and if there are, they will appear.

router.js Core

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'
  }],
  // Let each page scroll to the top and change the pattern to mode: history
  scrollBehavior (to, from, savedPosition) {
    if (savedPosition) {
      return savedPosition
    } else {
      return { x: 0, y: 0 }
    }
  }
})

vuex part

This part, mainly the song, because different pages have different uses of song information, put this part of data into vuex to do unified data processing! sotre/index.js

const store = new Vuex.Store({
  state: {
    audio: {
      'id': 0,
      'name': 'Name of song',
      'singer': 'Singer',
      'albumPic': '/static/player-bar.png',
      'location': '',
      'album': ''
    },
    lyric: 'Loading.',
    currentIndex: 0, // Location of the song currently playing
    playing: false, // Is it playing now?
    loading: false, // Is it loading?
    showDetail: false,
    songList: [],    // Playlist
    currentTime: 0,
    tmpCurrentTime: 0,
    durationTime: 0,
    bufferedTime: 0,
    change: false   // Judge whether it's a change of time or a playback time
  },
  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': 'Name of song',
          'singer': '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) { // Play the next song
      state.currentIndex++
      if (state.currentIndex > state.songList.length) {
        state.currentIndex = 1
      }
      state.audio = state.songList[state.currentIndex - 1]
    },
    playPrev (state) { // Play the last song
      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) { // Detecting song repetition
        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
    }
  },
  // Asynchronous data manipulation
  actions: {
    getSong ({commit, state}, id) {
      commit('openLoading')
      Axios.get(api.getSong(id)).then(res => {
        // Unified data model to facilitate the change of background interface
        var url = res.data.data[0].url
        commit('setAudio')
        commit('setLocation', url)
      })
    },
    getLrc ({commit, state}, id) {
      commit('setLrc', '[txt](Loading...')
      Axios.get(api.getLrc(id)).then(res => {
        // 1. Judge whether there are lyrics first
        if (res.data.nolyric) {
          commit('setLrc', '[txt](⊙0⊙) No lyrics yet')
        } else {
          console.log(res.data.lrc.lyric)
          commit('setLrc', res.data.lrc.lyric)
        }
      })
    }
  }
})

Final point project screenshot

github project address: https://github.com/javaSwing/NeteaseCloudWebApp

https://github.com/javaSwing

At present, it only completes the app song list part, which is also the core part. This project will be updated all the time! If you think it's good, give it a star t.

Topics: Vue github axios encoding