vue移动端音乐----播放器组件的开发2

播放模式对应到vuex中state是一个mode字段

export const playMode = {
  sequence: 0,
  loop: 1,
  random: 2
}
import {playMode} from 'common/js/config'  
 mode: playMode.sequence, // 默认为顺序播放

getter中有一个state的映射

export const mode = state => state.mode

所以在plyer中我们可以添加一个mapGetter中添加一个mode,这样我们就可以通过this.mode访问到我们当前的播放模式

    computed: {
        ...mapGetters([
            'fullScreen',
            'playlist',
            'currentSong',
            'playing', // 获取播放的状态
            'currentIndex',
            'mode'

        ])

之后,关于播放模式的icon就不可以写死了

 

 之后添加计算属性iconMode

import {playMode} from 'common/js/config'
iconMode() {
          // 在config中定义了mode为常亮0,1,2,要要先把他们拿出来
          return this.mode === playMode.sequence ? 'icon-sequence' : this.mode === playMode.loop ? 'icon-loop' : 'icon-random'
        }

然后在按钮上添加点击事件

 

先拿到当前的mode的mutation

...mapMutations({
           setFullScreen: 'SET_FULL_SCREEN',
           setPlayingState: 'SET_PLAYING_STATE',
           setCurrentIndex: 'SET_CURRENT_INDEX',
           setPlayMode: 'SET_PLAY_MODE'
        })

之后既可以通过setPlayMode改变mode的值

changeMode() { 每点击一下就加一,这样每点击一次就改变一次他的mode
        const mode = (this.mode + 1) % 3
        //  // mode要通过vux的mutation将它设置到state上
        this.setPlayMode(mode)
      },

这样我们通过点击就完成了样式的改变,但是播放列表还没有改变

2)修改播放模式的本质就是修改state中的播放列表playList[],所以在changMode的时候还要修改palylist[]播放列表,首先获取原始列表

    ...mapGetters([
            'fullScreen',
            'playlist',
            'currentSong',
            'playing', // 获取播放的状态
            'currentIndex',
            'mode',
            'sequenceList'
        ]),

取到原始列表之后,回到changeMode函数中,在改变mode的样式属性的同时修改播放列表

import {shuffle} from 'common/js/util' 
changeMode() { // 每点击一下就加一,这样每点击一次就改变一次他的mode
        const mode = (this.mode + 1) % 3
        // mode要通过vux的mutation将它设置到state上
        this.setPlayMode(mode)
        let list = null
        if (mode === playMode.random) {
          // 对sequenceList洗牌
          list = shuffle(this.sequenceList)
        } else {
          list = this.sequenceList
        }
        // 切换播放模式的时候currentSong并不改变,重新设置currentSong的id
        this.resetCurrentIndex(list)
        // 修改当前的playlist为改变mode之后的list,在getMutations中拿到playlist
        this.setPlaylist(list)
      }

其中,洗牌函数shuffle定义在js->util.js中,原理就是遍历数组,(let i = 0; i < _arr.length; i++),从0-i之间随机取一个数,与当前的arr[i]作交换,这样就把数组洗的很乱

function getRandomInt(min, max) { // 返回min和max之间的一个随机数,包括min和max
  return Math.floor(Math.random() * (max - min + 1) + min) // +1是保证可以取到上限值
}

export function shuffle(arr) { // 洗牌函数
  let _arr = arr.slice()
  for (let i = 0; i < _arr.length; i++) {
    let j = getRandomInt(0, i)
    let t = _arr[i]
    _arr[i] = _arr[j]
    _arr[j] = t
  }
  return _arr
}

list更新之后,应该讲数据提交到state,即修改当亲的playList,还是通过maoMutations做映射

        ...mapMutations({
           setFullScreen: 'SET_FULL_SCREEN',
           setPlayingState: 'SET_PLAYING_STATE',
           setCurrentIndex: 'SET_CURRENT_INDEX',
           setPlayMode: 'SET_PLAY_MODE',
           setPlaylist: 'SET_PLAYLIST'
        })

回到changeMode中添加setPlaylist方法,将我们洗牌或者没洗牌之后的list传入

 // 修改当前的playlist为改变mode之后的list,在getMutations中拿到playlist
        this.setPlaylist(list)

在getter.js中,我们知道currentSong是根据palyList和currentIndex计算而来的

export const currentSong = (state) => { // 可以担任计算属性,当前播放歌曲
    return state.playlist[state.currentIndex] || {}
}

但是我们希望切换播放模式的时候,currentSong并不改变,所以在changeMode中,我们要设置currentIndex,这里我们重新定义一个方法resetCurrentIndex,使我们在切换palyList的时候,currentIndex不发生改变

      resetCurrentIndex(list) {
        // findIndex是es6的一个语法,接受一个函数,函数可以拿到每一个list元素
        let index = list.findIndex((item) => {
          // 将当前歌曲的索引赋值给item的索引
          return item.id === this.currentSong.id
        })
        this.setCurrentIndex(index) // setMutaions方法设置当前的index
      }

在changeMode函数中,将resetCurrentIndex放在setPlaylist的前面

      changeMode() { // 每点击一下就加一,这样每点击一次就改变一次他的mode
        const mode = (this.mode + 1) % 3
        // mode要通过vux的mutation将它设置到state上
        this.setPlayMode(mode)
        let list = null
        if (mode === playMode.random) {
          // 对sequenceList洗牌
          list = shuffle(this.sequenceList)
        } else {
          list = this.sequenceList
        }
        // 切换播放模式的时候currentSong并不改变,重新设置currentSong的id
        this.resetCurrentIndex(list)
        // 修改当前的playlist为改变mode之后的list,在getMutations中拿到playlist
        this.setPlaylist(list)
      }

暂停状态下去切换播放列表mode,虽然图标显示的是暂停状态,但是歌曲却在播放,这是因为我们改变了playList,改变了currentSong,但是我们的currentSong还是会去出发watch,因为currentSong还是改变了,只不过我们限制它在列表中id没有变化,所以我们在这里要加一个判断,那是因为currentSong回调一旦执行,就会执行this.$ref.audio.play()进行播放

我们在currentSong改变的时候判断他的id有没有发生改变,如果没又发生改变的话就直接return

 currentSong(newSong, oldSong) {
           // 切换之后id没变我们就什么都不做
          if (newSong.id === oldSong.id) {
            return
          }
            // dom ready之后才可以掉用src,此处用nextTick加一个延时
            this.$nextTick(() => {
                this.$refs.audio.play()
            })
        },

我们实现了播放模式切换的功能,发现一个问题,当我把播放进度切换到末尾,我们发现这个歌曲没有自动播放了,这是因为audio中没有切换到下一首这个功能

7。歌曲播完之后没有继续播放下一首,在audio中添加触发事件@ended

    
     end() {
        if (this.mode === playMode.loop) { // 单曲循环
          this.loop()
        } else {
          this.next() // next函数之前定义过了
        }
      },
      loop () {
        // 歌曲回到起点并重新播放
        this.$refs.audio.currentTime = 0
        this.$refs.audio.play()
      }

点击进度条的btn时,我们获取的offsetX是有问题的,回到progress-bar

// 点击进度条的btn时,我们获取的offsetX是有问题的
      progressClick(e) {
        const rect = this.$refs.progressBar.getBoundingClientRect() // 用来获取进度条最左边到屏幕最左边的位置
        const offsetWidth = e.pageX - rect.left // pageX是我们点击的点到屏幕最左边的距离
        this._offset(offsetWidth)
        // 这里当我们点击 progressBtn 的时候,e.offsetX 获取不对
        // this._offset(e.offsetX)
        this._triggerPercent()
      }

8.实现歌手封皮上的随机播放按钮,
1)从play.vue回到music-list.vue组件

      
       
                    随机播放全部        

random和之前的selectPlay是一样的,也是要去创建一个actions
首先回到actions.js中创建randomPlay方法

// randomPlay是不需要索引index的
export const randomPlay = function ({commit}, {list}) {
    commit(types.SET_PLAY_MODE, playMode.random) // 首先设置播放模式
    commit(types.SET_SEQUENCE_LIST, list)
    // 对顺序播放的列表进行重新洗牌
    let randomList = shuffle(list)
    commit(types.SET_PLAYLIST, randomList)
    commit(types.SET_CURRENT_INDEX, 0) // 从第一个开始播放就可以了
    commit(types.SET_FULL_SCREEN, true)
    commit(types.SET_PLAYING_STATE, true)
}

回到music-list.vie中去使用

   ...mapActions([
        'selectPlay',
        'randomPlay'
      ])

然后在random函数中就可以调用randomPlay()函数

random() {
        this.randomPlay({
          list: this.songs
        })
      }

2)点击随机播放之后,我们在歌手的歌曲列表中在重新点击一首歌,发现播放的是另一首歌,因为我们在点击歌曲列表的时候 

import * as types from './mutation-types'
import {playMode} from 'common/js/config'
import { shuffle } from 'common/js/util'

function findIndex (list, song) {
    return list.findIndex((item) => {
        return item.id === song.id
    })
}
// 可以解构为commit和state的一个对象 ,有commit方法和state属性,可以拿到state,
// 第二个参数是一个payload,告知列表和索引
export const selectPlay = function ({commit, state}, {list, index}) {
    // 提交mutations,设置list,默认是顺序播放
    commit(types.SET_SEQUENCE_LIST, list) 
    // 点击随机播放之后,我们在歌手的歌曲列表中在重新点击一首歌,发现播放的是另一首歌,
    // 因为我们在点击歌曲列表的时候调用的是actions.js中的selectPlay,不是randomPlay,所以在selectPlay函数中做一个判断
    if (state.mode === playMode.random) {
        let randomList = shuffle(list)
        commit(types.SET_PLAYLIST, randomList)
        // 顺序列表中的index对应到随机列表中的index是怎样的
        index = findIndex(randomList, list[index])
    } else {
        commit(types.SET_PLAYLIST, list)
    }
    commit(types.SET_CURRENT_INDEX, index)
    commit(types.SET_FULL_SCREEN, true)
    commit(types.SET_PLAYING_STATE, true)
}

 // 定义一个副本洗牌之后不会改变原数组

export function shuffle(arr) { // 洗牌函数
  let _arr = arr.slice() // 定义一个副本洗牌之后不会改变原数组
  for (let i = 0; i < _arr.length; i++) {
    let j = getRandomInt(0, i)
    let t = _arr[i]
    _arr[i] = _arr[j]
    _arr[j] = t
  }
  return _arr
}

8. 播放器歌词数据的抓取

直接访问fcg的时候拿不到数据,使用express代理服务器
1)在歌曲播放界面,fcg_query_lyric_new.fcg中的preview中lyric是base64格式的歌词,同歌曲MP4资源的获取一样,用axiso代替jsonp进行后端代理请求自己的数据库

src-api-song.js中模拟前端调用

import {commonParams} from './config'
import axios from 'axios'
export function getLyric(mid) { // 传递一个歌曲的id
    const url = '/api/lyric' // 后端的代理地址

    const data = Object.assign({}, commonParams, {
        songmid: mid,
        platform: 'yqq',
        hostUin: 0,
        needNewCode: 0,
        g_tk: 254191688,
        pcachetime: +new Date(), // 当前的时间戳
        format: 'json' // 不是jsonp,是json 
    })
    return axios.get(url, {
        params: data // 参数通过params传进去
    }).then((res) => { // 拿到response,,放入promise中
        return Promise.resolve(res.data)
    })
}

前端调用写完之后去改写后端的逻辑,回到webpack.dev.conf.js中的before(app)中添加

const express = require('express')
const axios = require('axios')
const app = express()
var apiRoutes = express.Router()
app.use('/api', apiRoutes)

 before(app) {

            app.get('/api/lyric', (req, res) => {
        var url = 'https://c.y.qq.com/lyric/fcgi-bin/fcg_query_lyric_new.fcg'
        axios.get(url, {
          headers: {
            referer: 'https://c.y.qq.com/',
            host: 'c.y.qq.com'
          },
          params: req.query // 请求
        }).then((response)=>{ // 返回
          // res.json(response.data)
          var ret = response.data
          if (typeof ret === 'string') {
            var reg = /^\w+\(({[^()]+})\)$/  // 匹配正则表达式取到callback中的字符
            var matches = ret.match(reg)
            if (matches) {
              ret = JSON.parse(matches[1]) // 第一个元素是整个字符,第二个元素就是我们第一个括号所捕获的内容,这里取第二个元素
            }
          }
          res.json(ret) // 转化成json
        }).catch((error)=>{
          console.log(error)
        })
      })
    }
  }

配置完服务器之后,我们什么时候调用api->song.js中的getLyric方法呢,,在common->js->song.js中,我们之前调用了一个song类,在song中我们拿到了song的id,mid,歌名,歌手等数据,但是我们不能直接拿到歌词,这里我们在song类中封装一个方法,取得song的歌词

import { getLyric } from 'api/song'
import {ERR_OK} from 'api/config'
import {Base64} from 'js-base64'

export default class Song {
  constructor({id, mid, singer, name, album, duration, image, url}) {
    this.id = id
    this.mid = mid
    this.singer = singer
    this.name = name
    this.album = album
    this.duration = duration
    this.image = image
    this.url = url
  }
 
  // 调用getLyric api接口
  getLyric() {
    // 已经有lyric的歌曲不必每次都执行获取歌词操作
    if (this.lyric) {
      return Promise.resolve(this.lyric) // 因为getLyric返回的是一个promise
    }
    return new Promise((resolve, reject) => {
      getLyric(this.mid).then((res) => {
        if (res.retcode === ERR_OK) {
          this.lyric = Base64.decode(res.lyric)
          resolve(this.lyric) // 获得歌词
        } else {
          reject('no lyric')
        }
      })
    })
  }
}

在player中调用getLyric方法,watch到currentSong的时候

currentSong(newSong, oldSong) {
           // 切换之后id没变我们就什么都不做
          if (newSong.id === oldSong.id) {
            return
          }
            // dom ready之后才可以掉用src,此处用nextTick加一个延时
            this.$nextTick(() => {
                this.$refs.audio.play()
                // 测试歌词的输出
                // this.currentSong.getLyric() 
                this.getLyric()
            })
        },

歌词数据的抓取完成

2)将base64的字符串进行解码,第三方库,支持npm安装,在src->common->js-song.js中import

 this.lyric = Base64.decode(res.lyric)
import {Base64} from 'js-base64'

base64解码后的字符串是时间+歌词的形式

3)解析歌词字符串,引入第三库,lyric-parser,支持传入一个Lyric的String和一个handler,new Lyric(lyricStr, handler),没执行到一个时间点的时候都会执行handler函数,支持播放,停止,滚动到一个指定的时间和修改状态,在play.vue中

我们是在currentSong变化的时候回去调用song的getLyric函数,如果函数每次都执行,那么每次都有一个请求,显然是不合理的,所以要做一个判断,有Lyric,就直接return,并将其封装到promise中,然后去外部函数中处理歌词获取后数据的处理

 // 调用getLyric api接口
  getLyric() {
    // 已经有lyric的歌曲不必每次都执行获取歌词操作
    if (this.lyric) {
      return Promise.resolve(this.lyric) // 因为getLyric返回的是一个promise
    }
    return new Promise((resolve, reject) => {
      getLyric(this.mid).then((res) => {
        if (res.retcode === ERR_OK) {
          this.lyric = Base64.decode(res.lyric)
          // console.log(this.lyric);
          resolve(this.lyric) // 获得歌词
        } else {
          reject('no lyric')
        }
      })
    })
  }

在player组件中使用lyric-parser组件

currentSong(newSong, oldSong) {
           // 切换之后id没变我们就什么都不做
          if (newSong.id === oldSong.id) {
            return
          }
            // dom ready之后才可以掉用src,此处用nextTick加一个延时
            this.$nextTick(() => {
                this.$refs.audio.play()
                // 测试歌词的输出
                // this.currentSong.getLyric() 
                this.getLyric()
            })
        },

我们直接调用this.currentSong.getLyric()而是去封装一个getLyric函数
编写getLyric函数

import Lyric from 'lyric-parser'

 data() {
        return {
            currentLyric: null 
        }
    }

 

        getLyric() {
          // 对应result中的lyric,在zhen中拿到lyric的值
          this.currentSong.getLyric().then((lyric) => {
            this.currentLyric = new Lyric(lyric, this.handleLyric)
            // 输出lyric-parser之后,字符串被解析的样式
           // console.log(this.currentLyric) // lyric下对应一些对象,每个对象对应一个time和txt
           if (this.playing){ // 当歌曲正在播放时
             this.currentLyric.play() // 调用play,歌词播放,但是不能滚动
           }
          })
        },


这样time和text的对应关系就被解析出来了存在数组中,整体的歌词呗放在lrc中

9,歌词滚动列表的实现

处理后的数据格式lines(time,txt),在html中添加DOM
 

          
            

{{line.txt}}

我们getLyric中拿到歌词以后,调用play

getLyric() {
          // 对应result中的lyric,在zhen中拿到lyric的值
                  this.currentSong.getLyric().then((lyric) => {
            this.currentLyric = new Lyric(lyric, this.handleLyric)
            // 输出lyric-parser之后,字符串被解析的样式
           // console.log(this.currentLyric) // lyric下对应一些对象,每个对象对应一个time和txt
           if (this.playing){ // 当歌曲正在播放时
             this.currentLyric.play() // 调用play,歌词播放,但是不能滚动
           }
          })
        }

但是这个时候我们虽然调用了play函数,歌曲播放,但是我们还是不能看到歌词滚动

我们在new Lyric初始化lyric数据的时候,传递一个回调函数this.handleLyric,每一行歌词发生改变的时候,就调用回调函数

getLyric() {
          // 对应result中的lyric,在zhen中拿到lyric的值
                  this.currentSong.getLyric().then((lyric) => {
            this.currentLyric = new Lyric(lyric, this.handleLyric)
            // 输出lyric-parser之后,字符串被解析的样式
           // console.log(this.currentLyric) // lyric下对应一些对象,每个对象对应一个time和txt
           if (this.playing){ // 当歌曲正在播放时
             this.currentLyric.play() // 调用play,歌词播放,但是不能滚动
           }
          })
        },
        // 歌词发生改变的时候就进行的回调函数
        handleLyric({lineNum, txt}) { // 第几行歌词,和这行歌词的内容
          // 绑定DOM的:class,使当前的歌词变高亮
          this.currentLineNum = lineNum
        }

在data中定义currentLineNum,表示当前是第几行,并在DOM中bind的一个class为current,所以在handleLyric中将当前的lineNum传给currentLineNum

                

{{line.txt}}

2)实现歌词的滚动,在scroll中传入data,当lines发生变化时,可以进行scroll的刷新,因为在scroll中watch data'

3)使当前歌词一直在屏幕中间,自动滚动,首先获得歌词的引用ref

        // 歌词发生改变的时候就进行的回调函数
        handleLyric({lineNum, txt}) { // 第几行歌词,和这行歌词的内容
          // 绑定DOM的:class,使当前的歌词变高亮,将当前的lineNum传递给自定义属性currentLineNum,然后再去DOM中做判断
          this.currentLineNum = lineNum
          // 前5行是不用滚动scroll的
          if (lineNum > 5) {
            // 定位当前滚动的p标签的位置,让行数减5是为了让我们的歌词出现在屏幕中间
            let lineEl = this.$refs.lyricLine[lineNum - 5]
            // 滚动到p标签的位置,并有1s的动画时间
            this.$refs.lyricList.scrollToElement(lineEl, 1000)
          } else { // 5行之内直接滚动到顶部
            this.$refs.lyricList.scrollTo(0, 0, 1000)
          }
        }

因为每一行歌词都执行了回调,所以即使我们拖动歌词到别的位置,在执行下一行歌词的时候也会回到原来的位置

我们在调试的时候都是给控制台删除播放器界面只显示歌词界面,现在我们为两个界面添加左右滑动的特效

4)唱片界面和歌词界面可以左右滑动,添加dot,用来左右滑动,data中维护currentShow的值,默认是cd

 
                                 

用currentShow来维护两个点的状态。默认显示cd播放器界面为active状态,active在css中已经定义了

    data() {
        return {
            songReady: false,
            currentTime: 0,
            radius: 32,
            currentLyric: null, // 当前这首歌的歌词
            currentLineNum: 0, // 当前歌词所在的行 
            currentShow: 'cd'
        }
    }

之后添加左右滑动的效果,且当歌词界面从右向左滑动时,cd界面有一个渐隐的效果

    created() { // touch不需要添加getter和setter,所以定义在created中
      this.touch = {} // 用来关联touchStart等动作的
    }

在middleDOM绑定touch事件,前两个事件记得加prevent

下边分别实现这三个回调函数

middleTouchStart(e) {
          // 设置标志位表示已经初始化了
          this.touch.initiated = true
          const touch = e.touches[0]
          this.touch.startX = touch.pageX
          this.touch.stratY = touch.pageY
        },
        middleTouchMove(e) {
          if (!this.touch.initiated) {
            return
          }
          const touch = e.touches[0]
          // 拿到差值
          const deltaX = touch.pageX - this.touch.startX
          const deltaY = touch.pageY - this.touch.stratY
          // 歌词在纵向滚动,当纵向偏移大于左右偏移的时候,我们就不应该左右移动
          if (Math.abs(deltaY) > Math.abs(deltaX)) {
            return 
          }
          // 拿到滚动过程中middle-r距离左右两侧的屏幕的差值
          const left = this.currentShow === 'cd' ? 0 : -window.innerWidth
          const offsetWidth = Math.min(0, Math.max(-window.innerWidth, left + deltaX))
          this.touch.percent = Math.abs(offsetWidth / window.innerWidth)
          // lyricList是一个Vue(scroll)组件,是无法直接造作它的dom的,用$el来代替它的dom
          this.$refs.lyricList.$el.style[transform] = `translate3d(${offsetWidth}px,0,0)`
          this.$refs.lyricList.$el.style[transitionDuration] = 0
         // 透明度的渐变
         this.$refs.middleL.style.opacity = 1 - this.touch.percent
         this.$refs.middleL.style[transitionDuration] = 0
        },
        middleTouchEnd() {
          // 用来决定停在哪个位置
          let offsetWidth
          let opacity
          if (this.currentShow === 'cd') {
            // 从右向左滑,只需要滑10%就行
            if (this.touch.percent > 0.1) {
              offsetWidth = -window.innerWidth // 最终停止的位置
              opacity = 0
              this.currentShow = 'lyric' // 改变dot的css样式
            } else { 
              offsetWidth = 0 // 否则就回到原来的位置 
              opacity = 1
            }
          } else { // 从右向左滑,看的是0.9 
            if(this.touch.percent < 0.9) {
              offsetWidth = 0
              this.currentShow = 'cd'
              opacity = 1
            } else {
              offsetWidth = -window.innerWidth
              opacity = 0
            }
          }
          const time = 300
          this.$refs.lyricList.$el.style[transform] = `translate3d(${offsetWidth}px,0,0)`
          // 添加动画效果
          this.$refs.lyricList.$el.style[transitionDuration] = `${time}ms`
          this.$refs.middleL.style.opacity = opacity
          this.$refs.middleL.style[transitionDuration] = `${time}ms`
          this.touch.initiated = false
        }

切换歌曲的时候,歌词一直在跳,因为歌词是用currentLyric这个对象的内部函数完成歌词的不断跳跃,每次currentSong改变得时候,我们都会new一个新的lyric-parser出来的对象,但是我们之前的对象并没有清除,他还是有一个计时器在里面,所以就造成了歌词来回闪动这样一个bug,所以在watch中getLyric之前,我们要把当前的currentLyric给stop掉

 currentSong(newSong, oldSong) {
           // 切换之后id没变我们就什么都不做
          if (newSong.id === oldSong.id) {
            return
          }
          if(this.currentLyric) {
            this.currentLyric.stop()
          }
            // dom ready之后才可以掉用src,此处用nextTick加一个延时
            this.$nextTick(() => {
                this.$refs.audio.play()
                // 测试歌词的输出
                // this.currentSong.getLyric() 
                this.getLyric()
            })
        }

我们在点击-暂停的时候,歌词并没有停止,说明我们在点击togglePlay的时候,

 togglePlaying() {
            if(!this.songReady) {
              return 
            }
            this.setPlayingState(!this.playing)
            if(this.currentLyric) { // 点击暂停时,歌词没有在暂停
              this.currentLyric.stop().togglePlay()
            }
        }

循环播放同一首歌时,进度条拉倒最后,歌词并没有回到顶部,在loop中修改

 loop () {
        // 歌曲回到起点并重新播放
        this.$refs.audio.currentTime = 0
        this.$refs.audio.play()
         if(this.currentLyric) { // 将歌词平移到歌曲的开始
              this.currentLyric.seek(0)
            }
      }

拖动进度条的时候歌曲变了,但是歌词没有进行相应的滚动

 onProgressBarChange(percent) {
            // 设置audio的currentTime,当前歌曲的总时间乘以percent
            const currentTime = this.currentSong.duration * percent
            this.$refs.audio.currentTime = currentTime
            if (!this.playing) { // 若是拖动进度条之后暂停了,就变为继续播放
                this.togglePlaying()
            }
            if(this.currentLyric) { // 将歌词平移到歌曲的开始
              this.currentLyric.seek(currentTime * 1000)
            }
        }

在cd界面,cd的下方还有一个区块用来显示当前的歌词是什么,首先在DOM中添加区块

             
{{playingLyric}}

在data中定义playingLyric,因为他和DOM有定义

    data() {
        return {
            songReady: false,
            currentTime: 0,
            radius: 32,
            currentLyric: null, // 当前这首歌的歌词
            currentLineNum: 0, // 当前歌词所在的行 
            currentShow: 'cd',
            playingLyric: ''
        }
    }

playingLyric是在handleLyric回调函数执行的时候

   this.playingLyric = txt

到此,歌词部分正常情况就处理完了,之后处理异常情况。在getLyric中,如果获取不到歌词的时候,到做一些清理操作

        getLyric() {
          // 对应result中的lyric,在zhen中拿到lyric的值
            this.currentSong.getLyric().then((lyric) => {
            this.currentLyric = new Lyric(lyric, this.handleLyric)
            // 输出lyric-parser之后,字符串被解析的样式
           // console.log(this.currentLyric) // lyric下对应一些对象,每个对象对应一个time和txt
           if (this.playing){ // 当歌曲正在播放时
             this.currentLyric.play() // 调用play,歌词播放,但是不能滚动
           }
          }).catch(() => {
            this.currentLyric = null // 整个歌词置为空对象
            this.playingLyric = '' // cd下显示的歌词置为空
            this.currentLineNum = 0 
          })
        }

假设列表只有一条歌曲,我们点击下一步的时候会出现什么问题,在next函数中,currentINdex为0,index为1 ,满足if条件,index又被置为0,这个时候currentSong的id没有发生变化,这之后的逻辑就都不会执行,在这里我们要添加一个判断

 if (this.playlist.length) {
              this.loop()
            }

在prev中也添加同样的逻辑

在watch中修改微信端播放的问题

 // dom ready之后才可以掉用src,此处用nextTick加一个延时
            setTimeout(() => {
                this.$refs.audio.play()
                // 测试歌词的输出
                // this.currentSong.getLyric() 
                this.getLyric()
            },1000) // 微信从后台切换到前台时歌曲能够重新播放
        },

将mini-player放到下边之后,会影响scroll的高度,挡住原来屏幕中最下方的位置

 

################################################################
music-list.vue

你可能感兴趣的:(vue,移动端音乐)