輕量級音樂播放器搭建 3

輕量級音樂播放器搭建 3

 

接着以前的工做,如今想要對歌曲進行請求。首先應當啓動網易雲音樂的API的服務器,github地址以下。克隆這個項目後,在終端啓動:html

  npm install
node app.js

如今這個服務器就啓動了,默認爲3000端口。可是又有一個問題就是跨域,webpack沒有更改默認的配置的話實在本地服務器的8080端口,可是以上的網易雲API服務器的端口爲本地服務器的3000端口,因此端口不一致,不符合同源策略,因此這裏就須要使用axios。vue

axios是什麼?連接中是中文說明。聽說以前尤雨溪已經建議使用axios,VueResource再也不進行維護。axios就是一個基於Promise的http客戶端。安裝axios:html5

  
cnpm install axios --save

下面就是在webpack的本地服務器中配置axios,回到項目的根目錄中,找到dev-server.js這個文件,這個文件就是開發環境的服務器。就在這個文件中進行路由與axios請求轉發的配置。node

  ......
var axios = require('axios')
var app = express()

var apiRoutes = express.Router()

apiRoutes.get('/getSomething', function (req, res) {
 var url = 'https://anotherUrl.com/something'
 axios.get(url, {
   headers: {
     referer: 'https://anotherUrl.com/',
     host: 'anotherUrl.com'
  },
   params: req.query
}).then((response) => {
   res.json(response.data)
}).catch((e) => {
   console.log(e)
})
})

app.use('/api', apiRoutes)
......

由以上的代碼示例能夠看出,使用axios步驟以下:webpack

  1. 建立express.Router實例,用以得到路由。ios

  2. 對express.Router的實例進行監聽各類請求,如get等。當匹配到相應的url的時候,執行回調函數。git

  3. 編寫回調函數,回調函數用於對請求進行轉發,並獲取響應。因此函數中須要轉發的url,而後就能夠調用axios模塊的get方法(這裏以用戶發起get請求爲例)。axios的get方法有三個參數,第一個參數就是要進行轉發的url,這個必選;第二個參數是各類配置,包括headers,這個對於跨域的請求來講很是重要,由於他改變了請求頭部信息;汗包括一個參數就是params,就是請求所攜帶的參數。而後就是執行promise的then回調,將response的data傳回res的json。github

  4. 最後,將express框架應用實例調用use方法來使用這個router實例,當請求的路徑匹配到第一個參數的路徑的時候,就路由到apiRoutes這個模塊進行處理。web

因此在dev-server.js中添加以下代碼:vuex

  
// axios
var apiRouter = express.Router()
apiRouter.get('/getDefaultMusicList', function (req, res) {
 let url = 'http://localhost:3000/personalized/newsong'
 axios.get(url, {
   headers: {
     referer: 'http://localhost:3000/',
     host: 'localhost:3000'
  },
   params: req.query
}).then((response) => {
   console.log(response)
   res.json(response.data)
}).catch((err) => {
   console.log(err)
})
})
app.use('/api', apiRouter)

這樣的話,就能夠在music-player.vue中進行歌單的請求。可是先不要着急,由於比較好的思路與框架是進行模塊化,因而在src的common/api目錄中建立這些請求的功能,而後在vue組件中去引入使用這些模塊。因此先建立getDefaultMusicList.js文件:

  
import axios from 'axios'
export default function getDefaultMusicList () {
 return axios.get('/api/getDefaultMusicList')
  .then((res) => {
     return Promise.resolve(res.data)
  })
}

這裏就是使用了一個axios的http請求功能,地址就是本地服務器的地址加上路由的api(與以前在apiRouter實例中的路由對應),返回這個函數的請求返回值。那麼這個請求獲得數據以後又再一次的進行解析,將解析後的數據返回。如今能夠在music-player.vue中使用了:

  
<template>
 <div class="music-player">
   <header-bar></header-bar>
   <div class="mid">
     <img src="../../assets/logo.png">
   </div>
   <music-controller></music-controller>
 </div>
</template>


<script>
 import HeaderBar from 'components/header-bar/header-bar'
 import MusicController from 'components/music-controller/music-controller'
 import getDefaultMusicList from 'api/getDefaultMusicList'

 export default {
   components: {
     HeaderBar,
     MusicController
  },
   created () {
     console.log('MusicPlayer Created')
     this._getDefaultMusicList()
  },
   methods: {
     _getDefaultMusicList () {
       getDefaultMusicList().then((res) => {
           if (res.code === 200) {
             this.defaultMusicList = res.result
             console.log(this.defaultMusicList)
          }
        })
    }
  },
   data () {
     return {
       defaultMusicList: [],
    }
  }
}
</script>

如今若是打開瀏覽器的控制檯,就會看到當前這個music-player組件中的defaultMusicList的數據,咱們已經獲取到了,是一個長度爲10的數組。

如今要想播放一首歌曲,改怎麼辦呢?查詢API的文檔,找到了獲取音樂 url的接口。若是將獲取到的歌單中任一id做爲參數進行請求,在瀏覽器中會是以下的返回結果:

而後呢,返回值中data數組只有一個對象,對象裏有一個url,打開這個url就能夠聽到歌曲了。然而機智的我發現事情並非那麼簡單,歌曲的時間長度在哪?歌曲的背景圖片在哪?歌曲的各類參數都在哪?我靠這個list中的元素展開以後十分複雜。各類參數都不知道是幹什麼的,以第一個元素爲例,他的歌曲背景圖片的地址是在song->album->blurPicUrl之中。至於播放時間長度,這個好像是在song->bMusic/hMusic/lMusic/mMusic->playtime中。這幾個music我估計應該是音樂品質的區分吧。可是這個playtime怎麼解釋,這裏的數字是185696,臣妾想不通啊。計算得不到一個像是時間的結果。另外這個是在list中才有的屬性,若是在其餘的地方可能就沒有這個屬性了。真讓人頭大。算了這些先無論了,先用這個數據吧,至於播放時間就先不用了,也就是進度條暫時也不寫了。

那麼下面就是對歌單中的歌曲進行播放,我想要對列表進行循環播放。由於列表的長度是有限的可是不能播放完默認的列表就中止了,因此應當進行循環的播放。因此獲取完歌單以後就進行自動的播放。因此在api目錄中新建一個播放歌曲的函數,播放歌曲是另一個請求,因此仍是使用axios來進行轉發:

因此建立playThisMusic.js文件:

  
import axios from 'axios'

export default function playThisMusic (music) {
 let response = getMusicUrl(music.id)
 //axios.get('')
}

function getMusicUrl(id) {
 let url = `/api/getMusicUrl/url?id=${id}`
 return axios.get(url).then((res) => {
   console.log(res.data)
   return Promise.resolve(res.data)
})
}

這裏就是說如今有一個在defaultMusicList中的元素music。有了這個元素,能夠得到一些關於這個歌曲的信息,可是得到不了歌曲播放的地址。因此使用getMusicUrl方法來獲取歌曲播放的地址。那這個url地址怎麼得到呢?仍是要經過當前歌曲的id發送一個請求,而後再進行解析。可是仍是老問題,就是得到url又跨域了,因此仍是要再服務器端使用axios來發送請求。

因爲跨域發送的請求估計會有不少,因此我想把這個apiRouter作成一個模塊來引入到服務器端文件。因此再build目錄建立apiRouter文件,並進行修改與引用以下:

  
......
apiRouter.get('/getMusicUrl/url', function (req, res) {
 let url = `http://localhost:3000/music/url`
 axios.get(url, {
   headers: {
     referer: 'http://localhost:3000/',
     host: 'localhost:3000'
  },
   params: req.query
}).then((response) => {
   res.json(response.data)
}).catch((err) => {
   console.log(err)
})
})

module.exports = apiRouter

這裏有一點就是注意apiRouter所get的第一個url參數,參數必需要徹底匹配才能夠,若是隻寫爲'/getMusicUrl'則是匹配不到的。url部分就是一個請求的'?'以前的部分,以後爲params部分。而後做爲一個模塊,隨後應當使用module.exports來對apiRouter進行暴露出去。

如今再控制檯中就能夠看到有了返回的信息。是一個對象,對象的data部分是一個長度爲1的數組。數組中的url屬性就是咱們須要的播放地址。因此返回playThisMusic.js文件繼續對playThisMusic函數進行修改:

  
import axios from 'axios'

export function playThisMusic (music) {
 let url = ''
 getMusicUrl(music.id).then((res) => {
   if (res.code === 200) {
     url = res.data[0].url
  } else {
     console.log('未能獲取播放地址')
  }
})
}

export function getMusicUrl(id) {
 let url = `/api/getMusicUrl/url?id=${id}`
 return axios.get(url).then((res) => {
   console.log(res.data)
   return Promise.resolve(res.data)
})
}

我一開始覺得須要對這個播放的url進行請求才能播放音樂,結果發現不是這回事。在html5中,有專門的audio標籤來播放音頻文件。因此以上代碼中的playThisMusic方法就不須要了。修改music-player.vue文件:

  
<template>
 <div class="music-player">
   <header-bar></header-bar>
   <div class="mid">
     <audio :src="currentMusicUrl" autoplay></audio>
     <img src="../../assets/logo.png">
   </div>
   <music-controller></music-controller>
 </div>
</template>


<script>
 import HeaderBar from 'components/header-bar/header-bar'
 import MusicController from 'components/music-controller/music-controller'
 import getDefaultMusicList from 'api/getDefaultMusicList'
 import {getMusicUrl} from 'api/playThisMusic'

 export default {
   components: {
     HeaderBar,
     MusicController
  },
   created () {
     console.log('MusicPlayer Created')
     this._getDefaultMusicList()
  },
   methods: {
     _getDefaultMusicList () {
       getDefaultMusicList()
        .then((res) => {
           if (res.code === 200) {
             this.defaultMusicList = res.result
             console.log(this.defaultMusicList)
          }
        })
        .then(() => {
           this._playDefaultMusic(this.defaultMusicList.length - 1)
        })
    },
     _playDefaultMusic (lastIndex) {
       let currentIndex
       if (lastIndex === this.defaultMusicList.length - 1) {
         currentIndex = 0
      } else {
         currentIndex = lastIndex + 1
      }
       getMusicUrl(this.defaultMusicList[currentIndex].id)
        .then((res) => {
           this.currentMusicUrl = res.data[0].url
        })
    }
  },
   data () {
     return {
       defaultMusicList: [],
       currentMusicUrl: '',
    }
  }
}
</script>

怎麼沒有聲音?我找了半天的錯誤,發現緣由就是慢!音頻沒有緩衝好,可是耐心等待一會確實會聽到斷斷續續地播放的,至於爲何這麼慢我也不知道,若是直接請求音樂播放的url的話能夠瞬間打開,可是做爲audio標籤的src就很慢,我也不知道是爲何。欸,不行。如今直接請求的話直接沒有歌了,我猜多是由於網易雲音樂那邊的限制。

接着往下進行,如今若是要讓歌曲要自動調到下一個歌曲。通過查閱W3C文檔,發現audio元素有許多有用的事件。

Event name Dispatched when...
loadstart The user agent begins looking for media data, as part of the resource selection algorithm.
progress The user agent is fetching media data.
suspend The user agent is intentionally not currently fetching media data, but does not have the entire media resource downloaded.
abort The user agent stops fetching the media data before it is completely downloaded, but not due to an error.
emptied A media element whose networkState was previously not in the NETWORK_EMPTY state has just switched to that state (either because of a fatal error during load that's about to be reported, or because the load() method was invoked while the resource selection algorithm was already running).
error An error occurs while fetching the media data.
stalled The user agent is trying to fetch media data, but data is unexpectedly not forthcoming.
play Playback has begun. Fired after the play() method has returned, or when the autoplay attribute has caused playback to begin.
pause Playback has been paused. Fired after the pause() method has returned.
loadedmetadata The user agent has just determined the duration and dimensions of the media resource
loadeddata The user agent can render the media data at the current playback position for the first time.
waiting Playback has stopped because the next frame is not available, but the user agent expects that frame to become available in due course.
playing Playback has started.
canplay The user agent can resume playback of the media data, but estimates that if playback were to be started now, the media resource could not be rendered at the current playback rate up to its end without having to stop for further buffering of content.
canplaythrough The user agent estimates that if playback were to be started now, the media resource could be rendered at the current playback rate all the way to its end without having to stop for further buffering.
seeking The seeking IDL attribute changed to true and the seek operation is taking long enough that the user agent has time to fire the event.
seeked The seeking IDL attribute changed to false.
timeupdate The current playback position changed as part of normal playback or in an especially interesting way, for example discontinuously.
ended Playback has stopped because the end of the media resource was reached.
ratechange Either the defaultPlaybackRate or the playbackRate attribute has just been updated.
durationchange The duration attribute has just been updated.
volumechange Either the volume attribute or the muted attribute has changed. Fired after the relevant attribute's setter has returned.

對於要切換歌曲,時機就在於一首歌的結束位置。因此可使用ended事件來切換當前播放的音樂。因爲要切換歌曲,綁定事件等。因此要對audio元素綁定ended事件,所觸發的函數爲_playDefaultMusic,可是這個函數以前寫的須要傳遞一個當前的索引值。目前我想有兩種方案,一是在audio元素上綁定一個自定義特性index來表示索引;另外一個是不用傳遞索引,函數改成無參數,索引值改成由data保存(後期修改成vuex控制索引狀態)。顯然不管從代碼簡潔、資源控制仍是後期的擴展上都是第二種方式較好。

因爲初始的時候是聽第一首歌(其實第幾首根本無所謂),因此currentMusicIndex初始化爲個單列表的長度減一(這句話是後來補上的,不能初始化爲length - 1,由於這個數組實際上在一開始的時候是沒有定義的)。而後我想切換播放歌曲不論是點擊下一首也好,仍是左右滑動也好,仍是自動播放下一首也好,本質上都是對currentMusicIndex進行修改。因此能夠觀察currentMusicIndex這個變量的變化,若是有變化,那麼就切換資源而且播放音頻。修改代碼以下:

  
<template>
 <div class="music-player">
   <header-bar></header-bar>
   <div class="mid">
     <audio :src="currentMusicUrl" autoplay @ended="_playDefaultMusic(currentMusicIndex)" ref="audio"></audio>
     <img src="../../assets/logo.png">
   </div>
   <music-controller></music-controller>
 </div>
</template>


<script>
 import HeaderBar from 'components/header-bar/header-bar'
 import MusicController from 'components/music-controller/music-controller'
 import getDefaultMusicList from 'api/getDefaultMusicList'
 import {getMusicUrl} from 'api/playThisMusic'

 export default {
   data () {
     return {
       defaultMusicList: [],
       currentMusicUrl: '',
       currentMusicIndex: 0,
    }
  },
   components: {
     HeaderBar,
     MusicController
  },
   created () {
     console.log('MusicPlayer Created')
     this._getDefaultMusicList()
  },
   methods: {
     _getDefaultMusicList () {
       getDefaultMusicList()
        .then((res) => {
           if (res.code === 200) {
             this.defaultMusicList = res.result
             console.log(this.defaultMusicList)
          }
        })
        .then(() => {
           this._playDefaultMusic()
        })
    },
     _playDefaultMusic () {
       if (this.currentMusicIndex === this.defaultMusicList.length - 1) {
         this.currentMusicIndex = 0
      } else {
         this.currentMusicIndex = this.currentMusicIndex + 1
      }
       getMusicUrl(this.defaultMusicList[this.currentMusicIndex].id)
        .then((res) => {
           this.currentMusicUrl = res.data[0].url
        })
    }
  },
   watch: {
     currentMusicIndex: function (newVal, oldVal) {
       console.log(this.$refs.audio)
       this.$refs.audio.play()
    }
  }
}
</script>

能夠運行,可是報一個很詭異的錯誤:

  
Uncaught (in promise) DOMException: The element has no supported sources.

爲何呢?我想是由於一開始的時候audio中的src綁定的變量是currentMusicUrl,可是這個data初始化爲空字符串,然而我這裏play()方法調用的時機是在_playDefaultMusic中改變了currentMusicIndex,而後在修改的currentMusicUrl。因此會出現src沒有的狀況。而且還由別的bug。這個錯誤只報一次是由於最開始的一次直接沒有src。把兩段代碼交換一下位置,有什麼事明天再說。太晚了,得回去。

 

 

參考連接:

  1. axios中文說明

  2. axios github

  3. express 文檔

  4. 網易雲API文檔

  5. Promise 介紹

  6. audio W3C介紹

  7. vue watch 文檔

相關文章
相關標籤/搜索