vue音乐播放器笔记

简介: vue音乐播放器笔记

用户首先访问的是Home.vue组件,该组件在加载完成的时候调用created方法获取全部的歌单

(getSongsMenu)和歌曲(getAllSinger),   并将全部的歌单和歌手存放到了

songsMenuAndSinger中,这是个数组,里面存放的是两个对象,一个是歌单(每个歌单对象由多个songsMenuSong对象构成,每个songsMenuSong对象中包含一个song对象)对象另一个是歌曲对象。歌单和歌曲对象中又分别含有一个数组,用来分别存放歌单和歌曲。如下:songsMenuAndSinger: [

 {name: '推荐歌单>', list: []},

 {name: '热门歌手>', list: []}

]

此时,在Home.vue页面遍历songsMenuAndSinger数组。

<div class="section" v-for="(item, index) in songsMenuAndSinger" :key="index">
      <div class="section-title"><b>{{item.name}}</b></div>
      <content-list :contentList=item.list></content-list>
    </div>

其中item就指的是songsMenuAndSinger数组中的两个对象{name: '推荐歌单>', list: [ ]}和{name: '热门歌手>', list: []},然后将list传递给了ContentList.vue组件,由该组件进一步遍历。


props: [

 'contentList'],


也就是说Home.vue组件中item.list传递到了contentList中。

 <li class="content-item" v-for="(item, index) in contentList" :key="index">
        <div class="kuo" @click="goAblum(item, item.introduce)">
          <img class="item-img" :src="(item.picture)" alt="">
          <div class="mask"  @click="goAblum(item, item.name)">
          </div>
        </div>
        <p class="item-name"><b>{{item.name || item.title}}</b></p>
      </li>

这里的item代表的就是歌单和歌手对象了,第一次遍历的时候是歌单,第二次遍历的时候是歌手。因为歌单个歌手的是通过introduce字段区分的,因此如果当前是歌单的话会传递introduce参数,如果是歌手的话,会传递歌手的名字name参数。(下面以歌手为例,假设是第二次遍历,contentList中全是纯歌手对象)。


在点击歌手的头像后触发goAblum方法。

goAblum (item, type) {
  //将单个歌手或者是歌单放到缓存中,便于在页面跳转的时候取出。
  this.$store.commit('setSingerOrMenuTemp', item)
  if (type) {  //转向歌手页面
    this.$router.push({path: `/singer-album/${item.id}`})
  } else {    //转向歌单页面
    this.$router.push({path: `/song-list-album/${item.id}`})
  }
}

先将获取的该歌手对象保存到缓存singerOrMenuTemp中,当前是歌手触发的方法的话就保存的是一个歌手对象,反之,是一个歌单对象。然后携带个当前歌手(歌单)对象的参数id跳转到singerAlbum.vue页面中。

mounted () {
    this.singerId = this.$route.params.id // 从路由中获取歌手id
    this.singerOrMenu = this.singerOrMenuTemp   //从缓存获取歌手的信息
    this.getSongList()
  }

singerAlbum.vue页面一加载就获取路径参数id和缓存中存储的歌手(歌单)对象,其中获取id的目的是为了根据id查歌单或者是歌手的全部歌曲,在滴哦用this.getSongList()方法时用到了,如下。

 getSongList () {
      getSongOfSingerId(this.singerId)
        .then(res => {
          //这是要在播放列表显示的歌曲
          this.songs = res
          this.$store.commit("setListOfSongs",res)
        })
        .catch(err => {
          console.log(err)
        })
    }

然后就将获取的某个歌手或者是歌单的全部歌曲放到缓存中去了。此外将查询结果展示在当前页面上遍历获取的全部歌曲,如下.

<album-content :songList=this.songs> </album-content>

然后将获取的全部歌曲传递到了AlbumContent.vue页面,在该页面中遍历获取的歌曲,如下。

<li class="list-content" v-for="(item, index) in songList" :key="index">
        <div class="song-item" :class="{'is-play': id === item.id}"  @click="toplay(item.id, item.url, item.picture, index, item.name, item.lyrics)">
          <span class="item-index">
            <span v-if="id !== item.id">{{index + 1}}</span>
              <svg v-if="id === item.id" class="icon" aria-hidden="true">
                  <use xlink:href="#icon-yinliang"></use>
               </svg>
          </span>
          <span class="item-title">{{replaceFName(item.name)}}</span>
          <span class="item-name">{{replaceLName(item.name)}}</span>
          <span class="item-intro">{{item.introduction}}</span>
          <span class="item-intro">{{item.introduction}}</span>
        </div>
      </li>

如果是歌单的话稍微有些不同,因为每个歌单中的歌曲(也就是songsMenuSong中包含一个song对象)如下图。

  <li class="list-content" v-for="(item, index) in songList" :key="index">
              <div class="song-item" :class="{'is-play': id === item.id}"  @click="toplay(item.song.id, item.song.url, item.song.picture, index, item.song.name, item.song.lyrics)">
          <span class="item-index">
            <span v-if="id !== item.id">{{index + 1}}</span>
            <svg v-if="id === item.id" class="icon" aria-hidden="true">
              <use xlink:href="#icon-yinliang"></use>
            </svg>
          </span>
                <span class="item-title">{{item.song.name}}</span>
                <span class="item-name">{{item.song.name}}</span>
                <span class="item-intro">{{item.song.introduction}}</span>
                <span class="item-intro">时长</span>
              </div>
            </li>

此时,songList中存放的就是一个个的song对象了,:class="{'is-play': id === item.id}"表示如果缓存中的id和歌曲id一样的话激活该calss样式。


@click="toplay(item.id, item.url, item.picture, index, item.name, item.lyrics)调用的是minix中的播放方法,参数为当前歌曲的id,当前歌曲的播放地址,当前歌曲的图片地址,当前歌曲在当前歌曲列表中的索引位置,当前歌曲的名字和歌词。


当用户点击播放时,会触发toPlay方法,如下。

 toplay: function (id, url, picture, index, name, lyrics) {
      this.$store.commit('setId', id)
      this.$store.commit('setListIndex', index)
      this.$store.commit('setUrl', url)
      this.$store.commit('setpicUrl', picture)
      this.$store.commit('setTitle', this.replaceFName(name))
      this.$store.commit('setArtist', this.replaceLName(name))
      this.$store.commit('setLyric', this.parseLyric(lyrics))
      if (this.loginIn) {
        this.$store.commit('setIsActive', false)
        getCollectionOfUser(this.userId)
          .then(res => {
            for (let item of res) {
              if (item.songId === id) {
                this.$store.commit('setIsActive', true)
                break
              }
            }
          })
          .catch(err => {
            console.log(err)
          })
      }
    },

调用播放方法其实就是将这些数据存储到缓存中,this.loginIn是在用户登录后执行的,SongAudio.vue组件是根据url播放歌曲的,一旦缓存中的url改变,SongAudio.vue组件中播放的歌曲也会随着改变。如下。

<audio :src='url' controls="controls"
           ref="player" preload="true"
           @canplay="startPlay"
           @timeupdate="timeupdate"
           @ended="ended">
    </audio>

至此,点击播放列表中的歌曲可以切换播放。


接下来就是画一个好看的播放器了。就是playBar.vue组件。

 <div ref="progress" class="progress" @mousemove="mousemove">
            <!--进度条-->
            <div ref="bg" class="bg" @click="updatemove">
              <div ref="curProgress" class="cur-progress" :style="{width: curLength+'%'}"></div>
            </div>
            <!--进度条 end -->
            <!--拖动的点点-->
            <div ref="idot" class="idot" :style="{left: curLength+'%'}" @mousedown="mousedown" @mouseup="mouseup"></div>
            <!--拖动的点点 end -->
          </div>

progress指的是当前整个的进度条,curProgress指的是当前歌曲播放经过的区域,idot和curProgress的值是相同的都表示的是经过的区域。


点击播放按钮会触发事件togglePlay,判断取出缓存中的值isPlay,缓存中的isPlay默认是false,也就是不播放状态,执行else语句更新缓存中的isPlay为true,还要接着更新播放按钮的状态。如下。

 // 控制音乐播放 / 暂停
    togglePlay () {
      if (this.isPlay) {
        this.$store.commit('setIsPlay', false)
      } else {
        this.$store.commit('setIsPlay', true)
      }
    },
   //监控isPlay改变播放器的状态
    isPlay(){
      if(this.isPlay){
        this.$store.commit('setPlayButtonUrl', '#icon-zanting')
      }else{
        this.$store.commit('setPlayButtonUrl', '#icon-bofang')
      }
    },

进度条的拖拽原理:

首先需要在播放器页面也就是playbar.vue先计算好进度条的总长度如下:

this.progressLength = this.$refs.progress.getBoundingClientRect().width      //获取进度条的长度

当前播放歌曲的时间占歌曲总时长的百分比=当前歌曲的进度条占总进度条的百分比。如下,是当前播放时间占歌曲总时间的百分比。

this.curLength = (this.curTime / this.duration) * 100

如下,是歌曲的进度条占总进度条的百分比。点击事件 @click="updatemove"是拖动点点播放歌曲的时候触发该事件。

  <!--进度条-->
            <div ref="bg" class="bg" @click="updatemove">
              <div ref="curProgress" class="cur-progress" :style="{width: curLength+'%'}"></div>
            </div>

如下,是点点在进度条上的偏移量。

  <!--拖动的点点-->
            <div ref="idot" class="idot" :style="{left: curLength+'%'}" @mousedown="mousedown" @mouseup="mouseup"></div>
            <!--拖动的点点 end -->

鼠标点击点点的时候会使变量tag变为true ,并且获得开始移动的点点的X坐标,如下。

 mousedown (e) {
      this.mouseStartX = e.clientX
      this.tag = true
    },

当tag变为true的时候就说明当前是拖动点点的状态,进度条的位置也要随着改变,进度条从点点被点击开始算,为

let movementX = e.clientX - this.mouseStartX

其中 e.clientX表示的是当前的点点的位置,mouseStartX表示的是刚开始点点移动的位置,两者的差就是点点的位移。还要加上点点还没移动之前的进度条的长度,

this.progressLength = this.$refs.progress.getBoundingClientRect().width

两者相加就是当前的进度条的长度和点点的位置。

let newPercent = ((curLength + movementX) / this.progressLength) * 100

this.curLength = newPercent

this.mouseStartX = e.clientX  //重新为当前点点的位置赋值。

  mousedown (e) {
      this.mouseStartX = e.clientX
      this.tag = true
    },
 // 拖拽中
    mousemove (e) {
      if (!this.duration) {
        return false
      }
      if (this.tag) {
        let movementX = e.clientX - this.mouseStartX
        let curLength = this.$refs.curProgress.getBoundingClientRect().width
        //  计算出百分比
        this.progressLength = this.$refs.progress.getBoundingClientRect().width
        let newPercent = ((curLength + movementX) / this.progressLength) * 100
        if (newPercent > 100) {
          newPercent = 100
        }
        this.curLength = newPercent
        this.mouseStartX = e.clientX  //重新为当前点点的位置赋值。
        //  根据百分比推出对应的播放时间
        this.changeTime(newPercent)
      }
    },

然后还要改变播放时间, 就是根据当前的newPercent (也就是当前的进度占总进度条的长度的百分比)来更新时间,如下。

 // 更改歌曲进度
    changeTime (percent) {
      let newCurTime = this.duration * (percent * 0.01)
      this.$store.commit('setChangeTime', newCurTime)
    },

另外时间的格式需要转化成时分秒的形式,如下。

 // 播放时间的开始和结束
    curTime () {
      this.nowTime = this.formatSeconds(this.curTime)
      this.songTime = this.formatSeconds(this.duration)
      // 移动进度条
      this.curLength = (this.curTime / this.duration) * 100
    },
    // 解析播放时间
    formatSeconds (value) {
      let theTime = parseInt(value)
      let theTime1 = 0
      let theTime2 = 0
      if (theTime > 60) {
        theTime1 = parseInt(theTime / 60) // 分
        theTime = parseInt(theTime % 60) // 秒
        // 是否超过一个小时
        if (theTime1 > 60) {
          theTime2 = parseInt(theTime1 / 60) // 小时
          theTime1 = 60 // 分
        }
      }
      // 多少秒
      if (parseInt(theTime) < 10) {
        var result = '0:0' + parseInt(theTime)
      } else {
        result = '0:' + parseInt(theTime)
      }
      // 多少分钟时
      if (theTime1 > 0) {
        if (parseInt(theTime) < 10) {
          result = '0' + parseInt(theTime)
        } else {
          result = parseInt(theTime)
        }
        result = parseInt(theTime1) + ':' + result
      }
      // 多少小时时
      if (theTime2 > 0) {
        if (parseInt(theTime) < 10) {
          result = '0' + parseInt(theTime)
        } else {
          result = parseInt(theTime)
        }
        result = parseInt(theTime2) + ':' + parseInt(theTime1) + ':' + result
      }
      return result
    },

音量控件就是个图标加滑块,就是在mounted中设置监听图标点击事件,监听到了之后弹出滑块,在watch中监听滑块的值,并更新到缓存中,如下。

 document.querySelector('.icon-volume').addEventListener('click', function (e) {
      document.querySelector('.volume').classList.add('show-volume')
      e.stopPropagation()
    }, false)
    document.querySelector('.volume').addEventListener('click', function (e) {
      e.stopPropagation()
    }, false)
    document.addEventListener('click', function () {
      document.querySelector('.volume').classList.remove('show-volume')
    }, 


  volume () {
      this.$store.commit('setVolume', this.volume / 100)
    },

歌曲列表的组件是个公共组件,因此写到了compoents里面。 歌曲列表就是当前的歌曲列表只不过就是能在其他页面显示罢了(这里出现bug),,如下:

<template>
  <transition name="slide-fade">
    <div class="the-aside" v-if="this.showAside">
      <h2 class="title">播放列表</h2>
      <ul class="menus">
        <li v-for="(item, index) in listOfSongs" :class="{'is-play': id=== item.id}" :key="index" @click="toplay(item.id,item.url,item.picture,index,item.name,item.lyrics)">
          {{replaceFName(item.name)}}
        </li>
      </ul>
    </div>
  </transition>
</template>

上一曲和下一曲就是在获取当前歌曲在歌单中的位置的基础上索引的加减 ,以上一首为例,如下。

  prev () {
      if (this.listIndex !== -1 && this.listOfSongs.length > 1) { //当前歌单列表的数量是大于1的才会切换
        if (this.listIndex > 0) {   //不是第一首音乐
          this.$store.commit('setListIndex', this.listIndex - 1)  //将上一首歌曲在播放列表中的位置存放到缓存中。
          this.toPlay(this.listOfSongs[this.listIndex].url)         //播放当前的歌曲
        } else {
          this.$store.commit('setListIndex', this.listOfSongs.length - 1)
          this.toPlay(this.listOfSongs[this.listIndex].url)//切换到倒数第一首音乐,放到缓存中。
        }
      }
    },

当前歌曲播放完成后自动播放下一首歌曲,触发事件是在SongAudio.vue 中的end方法,该方法中改变autoNext的值,playBar组件中监控这个值的变化,只要改变就调用播放下一首的方法,如下。

 // 音乐播放结束时触发
    ended () {
      this.$store.commit('setIsPlay', false)
      this.$store.commit('setCurTime', 0)
      this.$store.commit('setAutoNext', !this.autoNext)
    }
// 自动播放下一首
    autoNext () {
      this.next()
    }
 // 下一首
    next () {
      if (this.listIndex !== -1 && this.listOfSongs.length > 1) {
        if (this.listIndex < this.listOfSongs.length - 1) {
          this.$store.commit('setListIndex', this.listIndex + 1)
          this.toPlay(this.listOfSongs[this.listIndex].url)
        } else {
          //否则就是最后一首(也就是第一首)
          this.$store.commit('setListIndex', 0)
          this.toPlay(this.listOfSongs[0].url)
        }
      }

至此整个播放器完毕。


对于歌词的解析,因为后台存入的歌词是带有时间 的如下,


                                                                     [01:22.290] 海上清辉与圆月 盛进杯光将所有歌词看作是一个数组,每行歌词是数组的一个数据。然后用正则表达式将数组中每个元素的时间和歌词分离。也就是如下格式:


                                                                   【【时间,歌词】,【时间,歌词】】

相关文章
|
3天前
|
JavaScript 前端开发
如何在 Vue 项目中配置 Tree Shaking?
通过以上针对 Webpack 或 Rollup 的配置方法,就可以在 Vue 项目中有效地启用 Tree Shaking,从而优化项目的打包体积,提高项目的性能和加载速度。在实际配置过程中,需要根据项目的具体情况和需求,对配置进行适当的调整和优化。
|
4天前
|
存储 缓存 JavaScript
在 Vue 中使用 computed 和 watch 时,性能问题探讨
本文探讨了在 Vue.js 中使用 computed 计算属性和 watch 监听器时可能遇到的性能问题,并提供了优化建议,帮助开发者提高应用性能。
|
4天前
|
存储 缓存 JavaScript
如何在大型 Vue 应用中有效地管理计算属性和侦听器
在大型 Vue 应用中,合理管理计算属性和侦听器是优化性能和维护性的关键。本文介绍了如何通过模块化、状态管理和避免冗余计算等方法,有效提升应用的响应性和可维护性。
|
4天前
|
存储 缓存 JavaScript
Vue 中 computed 和 watch 的差异
Vue 中的 `computed` 和 `watch` 都用于处理数据变化,但使用场景不同。`computed` 用于计算属性,依赖于其他数据自动更新;`watch` 用于监听数据变化,执行异步或复杂操作。
|
3天前
|
JavaScript 前端开发 UED
vue学习第二章
欢迎来到我的博客!我是一名自学了2年半前端的大一学生,熟悉JavaScript与Vue,目前正在向全栈方向发展。如果你从我的博客中有所收获,欢迎关注我,我将持续更新更多优质文章。你的支持是我最大的动力!🎉🎉🎉
|
5天前
|
存储 JavaScript 开发者
Vue 组件间通信的最佳实践
本文总结了 Vue.js 中组件间通信的多种方法,包括 props、事件、Vuex 状态管理等,帮助开发者选择最适合项目需求的通信方式,提高开发效率和代码可维护性。
|
3天前
|
JavaScript 前端开发 开发者
vue学习第一章
欢迎来到我的博客!我是瑞雨溪,一名热爱JavaScript和Vue的大一学生。自学前端2年半,熟悉JavaScript与Vue,正向全栈方向发展。博客内容涵盖Vue基础、列表展示及计数器案例等,希望能对你有所帮助。关注我,持续更新中!🎉🎉🎉
|
17天前
|
数据采集 监控 JavaScript
在 Vue 项目中使用预渲染技术
【10月更文挑战第23天】在 Vue 项目中使用预渲染技术是提升 SEO 效果的有效途径之一。通过选择合适的预渲染工具,正确配置和运行预渲染操作,结合其他 SEO 策略,可以实现更好的搜索引擎优化效果。同时,需要不断地监控和优化预渲染效果,以适应不断变化的搜索引擎环境和用户需求。
|
5天前
|
存储 JavaScript
Vue 组件间如何通信
Vue组件间通信是指在Vue应用中,不同组件之间传递数据和事件的方法。常用的方式有:props、自定义事件、$emit、$attrs、$refs、provide/inject、Vuex等。掌握这些方法可以实现父子组件、兄弟组件及跨级组件间的高效通信。
|
10天前
|
JavaScript
Vue基础知识总结 4:vue组件化开发
Vue基础知识总结 4:vue组件化开发

相关实验场景

更多