播放模式对应到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中添加区块
在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
:listen-scroll="listenScroll" :probe-type="probeType" class="list" ref="list">