輕量級音樂播放器搭建 5

輕量級音樂播放器搭建 5

 

首先安裝vuex,在根目錄(有package.json的目錄中)打開終端。css

  npm install vuex --save

等待安裝完成以後,在main.js文件中進行vuex的註冊。註冊步驟相似於vue-router,他們都是vue的官方插件。html

  ......
import store from './store'

new Vue({
 ......
 store
})

在src目錄中建立store目錄,這個目錄用於儲存關於vuex的一切。因此目錄中能夠建立若干js文件做爲模塊,分別有index.js,actions.js,getters.js,mutations.js,mutation-types.js。因爲項目不大,因此我只建立一個index.js,若是須要那麼之後再擴展。在main.js中引入的store是一個文件夾,他會自動搜尋index文件並做爲入口。vue

編輯index.js文件:vue-router

  
import Vuex from 'vuex'
import Vue from 'vue'

Vue.use(Vuex)

export default new Vuex.Store({
 // 狀態儲存值
 state: {
},
 // 狀態惟一的改變方法,commit調用
 mutations: {
},
 // 相似於computed計算屬性,接受state參數
 getters: {
},
 // 異步經過mutations修改狀態值,dispatch調用
 actions: {
}
})

以上就是vuex的大致結構,如今對於音樂播放器的控制來講,儲存與觀察的狀態有以下幾點:vuex

  1. 歌曲是否正在播放npm

  2. 歌曲時長json

  3. 歌曲是否標記爲喜歡api

  4. 播放模式異步

  5. 播放用戶列表音樂仍是播放推薦頻道svg

暫且想到了這麼多,其實還有當前播放歌曲索引,當前歌單什麼的。可是考慮到要修改以前的代碼,就先不考慮這個,等以後再修改。因此修改index.js的代碼:

  
 state: {
   // 是否正在播放
   isPlaying: false,
   // 是否點擊喜歡這是歌曲
   likeThisMusic: false,
   // 音樂時長
   musicDuration: 0,
   // 播放模式,0表明順序播放,1表明隨機播放,2表明單曲循環
   displayModel: 0,
   // 是否播放用戶自定義歌曲,若不是則爲播放推薦頻道
   privateSongList: false,
},
 mutations: {
   // 切換播放與暫停狀態
   togglePlayPause (state) {
     state.isPlaying = !state.isPlaying
  }
},

如今若是我想觀測某一個state,讓他在有變化的時候觸發某個函數應當怎麼作呢?好比這裏的isPlaying狀態,觀測值改變爲true的時候觸發播放,改變爲false的時候暫停。

十點半宿舍快要關門了,明天繼續。

這種狀況可使用computed屬性,官方文檔給出的例子以下:

  
const Counter = {
 template: `<div>{{ count }}</div>`,
 computed: {
   count () {
     // return store.state.count 也可使用注入的$store屬性:
     return this.$store.state.count
  }
}
}

而後若是須要多個狀態的時候,能夠私用mapState輔助函數。這個函數能夠幫助生成計算屬性,以下:

  
// 在單獨構建的版本中輔助函數爲 Vuex.mapState
import { mapState } from 'vuex'
export default {
 computed: mapState({
   // 箭頭函數可以使代碼更簡練
   count: state => state.count,
   // 傳字符串參數 'count' 等同於 `state => state.count`
   countAlias: 'count',
   // 爲了可以使用 `this` 獲取局部狀態,必 須使用常規函數
   countPlusLocalState (state) {
     return state.count + this.localCount
  }
})
}

另外若是state中的狀態名與組件中計算屬性名一致的話,也能夠簡寫爲一個字符串。以下:

  
computed: mapState([
 // 映射 this.count 爲 store.state.count
 'count'
])

因爲vuex我也是剛剛接觸,因此準備用與不用mapState都嘗試一下,因此先對代碼修改以下:

  
<template>
 <div class="music-controller" @click="togglePlayPause">
  新垣結衣的播放控制器
   <div class="control-panel">
   <i class="iconfont danqu" >&#xe620;</i>
   <i class="iconfont liebiao">&#xe6f2;</i>
   <i class="iconfont suiji">&#xe6f1;</i>
   <i class="iconfont xin">&#xe6ca;</i>
   <i class="iconfont shangyishou">&#xe600;</i>
   <i class="iconfont bofang">&#xe768;</i>
   <i class="iconfont zanting">&#xe602;</i>
   <i class="iconfont xiayishou">&#xe601;</i>
   <i class="iconfont xiangqing">&#xe64d;</i>
   </div>
 </div>
</template>


<script>
 export default {
   methods: {
     togglePlayPause () {
       console.log('clicked')
       this.$store.commit('togglePlayPause')
    },
     _logChanged () {
       console.log('STATE CHANGED!')
       console.log(this.$store.state.isPlaying)
    }
  },
   computed: {
     isPlaying () {
       return this.$store.state.isPlaying
    }
  },
   watch: {
     isPlaying () {
       this._logChanged()
    }
  }
}
</script>

如今若是點擊播放器控制的div就會觸發togglePlayPause函數,而後提交commit,執行togglePlayPause這個mutation,這樣就會isPlaying狀態就會發生變化。而後組件代碼中的computed屬性返回這個狀態。而後讓watch屬性來觀察這個computed屬性。如今state中的某個狀態屬性發生了變化的時候就會經過computed返回而且被watch觀察到。能夠在控制檯中看到每當點擊控制面板的時候就會顯示狀態發生了改變。

若是是使用mapState呢?這裏有個注意的地方,看到官方文檔中的mapState是直接代替爲compued的全部屬性,可是大部分狀況是不僅關心vuex中的state,並且還有組件中的一些狀態須要計算屬性。這個時候就須要使用ES6的擴展運算符,將多個屬性打包成一個對象:

  
<template>
 <div class="music-controller">
  新垣結衣的播放控制器
   <div class="control-panel">
   <i class="iconfont danqu" @click="changePlayMode">&#xe620;</i>
   <i class="iconfont liebiao" @click="changePlayMode">&#xe6f2;</i>
   <i class="iconfont suiji" @click="changePlayMode">&#xe6f1;</i>
   <i class="iconfont xin">&#xe6ca;</i>
   <i class="iconfont shangyishou">&#xe600;</i>
   <i class="iconfont bofang"  @click="togglePlayPause">&#xe768;</i>
   <i class="iconfont zanting"  @click="togglePlayPause">&#xe602;</i>
   <i class="iconfont xiayishou">&#xe601;</i>
   <i class="iconfont xiangqing">&#xe64d;</i>
   </div>
 </div>
</template>


<script>
 import store from 'store'
 import {mapState} from 'vuex'
 export default {
   methods: {
     togglePlayPause () {
       console.log('togglePlayPause')
       this.$store.commit('togglePlayPause')
    },
     changePlayMode () {
       console.log('changePlayMode')
       this.$store.commit('changePlayMode')
    },
     _logIsPlayingChanged () {
       console.log('STATE CHANGED!')
       console.log(this.$store.state.isPlaying)
    },
     _logPlayModeChanged () {
       console.log('PLAYMODE CHANGED!')
       console.log(this.$store.state.playMode)
    }
  },
   computed: {
     ...mapState([
       'isPlaying',
       'likeThisMusic',
       'playMode',
       'privateSongList'
    ])
  },
   watch: {
     isPlaying () {
       this._logIsPlayingChanged()
    },
     playMode () {
       this._logPlayModeChanged()
    }
  }
}
</script>

以上的代碼提供了兩個狀態屬性與狀態修改作實驗,如今點擊對應的圖標,就會有咱們須要的結果了。

如今vuex的使用方法就已經主要弄明白了,如今就是繼續對控制面板界面與邏輯進行編寫:

  
<template>
 <div class="music-controller">
  新垣結衣的播放控制器
   <div class="control-panel">
     <div  v-if="privateSongList" @click="changePlayMode">
       <i class="iconfont danqu" v-show="playMode === 0">&#xe620;</i>
       <i class="iconfont liebiao" v-show="playMode === 1">&#xe6f2;</i>
       <i class="iconfont suiji" v-show="playMode === 2">&#xe6f1;</i>
     </div>
     <div v-else @click="toggleLikeThisMusic">
       <i class="iconfont dislike" v-show="!likeThisMusic">&#xe6ca;</i>
       <i class="iconfont like" v-show="likeThisMusic">&#xe628;</i>
     </div>
     <i class="iconfont shangyishou" @click="changeToPrevMusic">&#xe600;</i>
     <i class="iconfont bofang"  @click="togglePlayPause" v-show="isPlaying">&#xe768;</i>
     <i class="iconfont zanting"  @click="togglePlayPause" v-show="!isPlaying">&#xe602;</i>
     <i class="iconfont xiayishou" @click="changeToNextMusic">&#xe601;</i>
     <i class="iconfont xiangqing" @click="showMusicDetail">&#xe64d;</i>
   </div>
 </div>
</template>

尚未加css樣式,這裏有個略微繞的問題就是這些狀態的命名與切換。由於在平常生活中,播放的時候顯示的是暫定圖標,因此在開發的時候就比較混亂,倒不是什麼大問題,注意一下就好。

  
<template>
 <div class="music-controller">
   <div class="control-panel">
     <div class="play-mode control-unit" v-if="privateSongList" @click="changePlayMode">
       <i class="iconfont danqu" v-show="playMode === 0">&#xe620;</i>
       <i class="iconfont liebiao" v-show="playMode === 1">&#xe6f2;</i>
       <i class="iconfont suiji" v-show="playMode === 2">&#xe6f1;</i>
     </div>
     <div class="like-this-music control-unit" v-else @click="toggleLikeThisMusic">
       <i class="iconfont dislike" v-show="!likeThisMusic">&#xe6ca;</i>
       <i class="iconfont like" v-show="likeThisMusic">&#xe628;</i>
     </div>
     <i class="iconfont shangyishou control-unit" @click="changeToPrevMusic">&#xe600;</i>
     <i class="iconfont bofang control-unit"  @click="togglePlayPause" v-show="isPlaying">&#xe768;</i>
     <i class="iconfont zanting control-unit"  @click="togglePlayPause" v-show="!isPlaying">&#xe602;</i>
     <i class="iconfont xiayishou control-unit" @click="changeToNextMusic">&#xe601;</i>
     <i class="iconfont xiangqing control-unit" @click="showMusicDetail">&#xe64d;</i>
   </div>
 </div>
</template>

......

<style scoped>
 @font-face {
   font-family: 'iconfont';  /* project id 431442 */
   src: url('//at.alicdn.com/t/font_431442_ylcevnla67xpqfr.eot');
   src: url('//at.alicdn.com/t/font_431442_ylcevnla67xpqfr.eot?#iefix') format('embedded-opentype'),
   url('//at.alicdn.com/t/font_431442_ylcevnla67xpqfr.woff') format('woff'),
   url('//at.alicdn.com/t/font_431442_ylcevnla67xpqfr.ttf') format('truetype'),
   url('//at.alicdn.com/t/font_431442_ylcevnla67xpqfr.svg#iconfont') format('svg');
}
 .iconfont {
   font-family: "iconfont";
   font-size: 40px;
   font-style: normal;
}
 .music-controller {
   width: 100%;
   height: 30%;
   display: flex;
   justify-content: center;
   align-items: center;
   background-color: darkcyan;
}
 .control-panel {
   display: flex;
}
 .control-unit {
   display: flex;
   align-items: center;
   margin: 15px;
}
 .dislike, .like {
   font-size: 34px;
}
 .like {
   color: indianred;
}
</style>

而後就是寫關於控制的邏輯。首先就是歌曲的暫停與播放,這裏就調用了組件中的togglePlayPause函數,可是這是一個播放器的子組件(audio元素並不在這裏),因此事實上的控制播放應當是傳遞給此組件的父組件。子組件向父組件傳遞消息是依靠自定義事件的觸發,因此這裏應當在此組件的togglePlayPause函數中觸發事件,並在父組件中進行監聽。

這裏其實能夠直接在父組件進行監聽播放的狀態,可是我想練習一會兒組件向父組件傳遞數據,因此就先這樣將播放與暫停的邏輯向父組件傳遞,其餘的狀態就轉移到父組件中進行監控。因此musiic-controller.vue組件與父組件music-player代碼作以下修改:

  
musiic-controller.vue:
......
<script>
 import store from 'store'
 import {mapState} from 'vuex'
 export default {
   methods: {
......
     _emitIsPlayingChanged () {
       console.log('IS PLAYING CHANGED!')
       this.$emit('isPlayingChanged', this.$store.state.isPlaying)
    }
  },
   computed: {
     ...mapState([
       'isPlaying',
    ])
  },
   watch: {
     isPlaying () {
       this._emitIsPlayingChanged()
    }
  }
}
</script>

music-player.vue
......
<template>
 <div class="music-player">
   <header-bar></header-bar>
   <div class="mid">
     <audio :src="currentMusicUrl" autoplay @ended="_playDefaultMusic(currentMusicIndex)" ref="audio"></audio>
     <img :src="currentMusicPictureSrc" class="music-album-picture">
   </div>
   <music-controller v-on:isPlayingChanged="togglePlaying"></music-controller>
 </div>
</template>

<script>
......
 export default {
......
   methods: {
     togglePlaying (isPlaying) {
       console.log(isPlaying)
       if (isPlaying === true) {
         this.$refs.audio.play()
      } else {
         this.$refs.audio.pause()
      }
    }
  }
}
</script>

我這裏對播放的控制是使用了audio對象的play與pause這兩個方法。audio對象的相關內容比較難找,我在一些博客上看到了一些關於audio對象的內容,放到了參考連接中。其中的fastseek等方法感受之後頗有可能用獲得。

 

參考連接:

  1. 擴展運算符

  2. vuex 輔助函數

  3. flex 佈局講解

  4. 移動端手勢事件

  5. 移動端手勢事件

  6. vue $emit 文檔

  7. audio 對象相關

  8. vuex 文檔

相關文章
相關標籤/搜索