轻量级音乐播放器搭建 5

轻量级音乐播放器搭建 5

 

首先安装vuex,在根目录(有package.json的目录中)打开终端。

  npm install vuex --save

等待安装完成之后,在main.js文件中进行vuex的注册。注册步骤类似于vue-router,他们都是vue的官方插件。

  ......
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文件并作为入口。

编辑index.js文件:

  
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的大体结构,现在对于音乐播放器的控制来说,储存与观察的状态有如下几点:

  1. 歌曲是否正在播放

  2. 歌曲时长

  3. 歌曲是否标记为喜欢

  4. 播放模式

  5. 播放用户列表音乐还是播放推荐频道

暂且想到了这么多,其实还有当前播放歌曲索引,当前歌单什么的。但是考虑到要修改之前的代码,就先不考虑这个,等之后再修改。所以修改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 文档

  9.  

 

posted @ 2017-10-12 17:27  虚几  阅读(275)  评论(0编辑  收藏  举报