播放器模式切换
1. 样式切换
这个太简单了,在前面已经写过好多个按钮图标的切换了。这里依赖的是 定义的全局属性 model.思路如下:
- 在计算属性中返回model值
- 在计算属性中 根据model值返回对应的icon图标样式
给按钮增加点击事件,并改写全局属性的model值
playModelChange () { let mode = (this.mode + 1) % 3 // 循环3次切换 this.setPlayModel(mode) }
2. 歌曲相关信息切换
这里切换播放模式的话,歌曲列表也是会打乱顺序然后被切换的(与自己想的不同,我一直以为列表是原始列表,只是播放的时候使用的是随机数播放)
所以 这里要做以下操作:
- 改变vuex中的播放列表(根据不同的播放模式)
- 如果播放列表改变了,当前歌曲的索引也要改变
playModelChange () {
let mode = (this.mode + 1) % 3 // 循环3次切换
this.setPlayModel(mode)
let list = null
if (mode === playMode.random) {
// 获得原始列表,打乱顺序后,再赋值给播放列表
list = shuffle(this.sequenceList)
} else {
list = this.sequenceList
}
this.setPlayList(list)
this._resetCurrentIndex(list)
},
_resetCurrentIndex (list) {
let songId = this.currentSong.id
let index = list.findIndex((item) => {
return item.id === songId
})
// 在getters中 currentSong 是根据索引获取到歌曲属性对象的
// 这里索引改变了,currentSong 也改变了,但是对象一样的,
// 因为在shuffle中现在的实现是在原来的列表中打乱顺序的,他们的内存地址一样
// 所以 为什么 还会触发watch中的currentSong事件呢?没有搞明白
// 但是现象就是这样的
this.setCurrentIndex(index)
}
上面用到的工具类 src/common/js/util.js
function getRandomInt (min, max) {
// random() 返回 0 -0.9999 之间的小数,
// 假设 min=0,max=20, 0.99 * (20-0 +1) = 20.79 + 0 ,再向下取整 就得到了最大值20
// 最小值也是一样。不错这个
return Math.floor(Math.random() * (max - min + 1) + min)
}
/**
* 洗牌:这里现在的实现 我觉得有问题,在原有基础上洗牌的,
* 根据功能来说,是完全没有毛病的;在是在外面使用的场景中,外面把原始列表传递进来了。原始列表的顺序都被改变了
* @param arr
*/
export function shuffle (arr) {
let length = arr.length
for (let i = 0; i < length; i++) {
let j = getRandomInt(0, i)
let a1 = arr[j]
arr[j] = arr[i]
arr[i] = a1
}
return arr
}
上面的代码写好后,播放模式个列表都ok了,大事有一个与之前冲突的地方:
如果当前正则暂停中,这个时候再切换播放模式的话,会让歌曲再次播放.解决方案如下:
- 先定位:改变了播放列表 和 歌曲索引,那么歌曲索引会英法 currentSong的变化
- currentSong的变化 会触发下面的watch中的 播放操作
只要对比下 新值和旧值的歌曲id 就知道是否是同一首歌曲了
只是这里没有明白的是,vue的watch工作原理判定是否相同的标准是什么?这个用 === 判定是相等,那为什么还会触发 watch 事件呢?
watch: {
// 当前歌曲变化的时候 播放歌曲
currentSong (newSong, oldSong) {
console.log('对比结果:', oldSong === newSong, ' oldSong:', oldSong, ' newSong:', newSong)
// 解决切换播放模式后,该事件就算歌曲 对比结果是相同的情况下还是会触发watch事件
// 如果这时 在暂停状态,那么如果没有这里的判断,就会触发下面的 play操作
if (newSong.id === oldSong.id) {
return
}
// 在dom没有变化之前调用play会出错,所以使用vue提供的dom更新后调用
this.$nextTick(() => {
this.$refs.audio.play()
})
},
}
3. 功能优化 - 歌曲结束后,自动播放下一曲
在前面的功能中,发现歌曲播放结束后,歌曲就不会再播放了。
audio
标签提供一个 onended
事件,可以用来触发业务动作
<audio ref="audio" :src="currentSong.url"
@canplay="ready" @error="error"
@timeupdate="timeupdate"
@ended="onended"
></audio>
// 歌曲播放结束
onended () {
if (this.mode === playMode.loop) {
// 如果是循环播放,则歌曲单曲循环 -- 尼玛,原来是单曲循环
this.loop()
} else {
this.next()
}
},
loop () {
this.$refs.audio.currentTime = 0
this.$refs.audio.play()
}
4. 功能优化 - 终于发现进度条点击两下获取的偏移值有问题了
在之前写长进度条的时候,发现连续点击两下进度条,进度条的宽度错得很离谱。
原来是 我们的点击事件写在了最外层的div上。而连续点击两次的时候,第一次是正常的,而第二次点击的时候是点到了 小圆点的按钮上了。所以获取到的偏移量是小远点的偏移量。所以看起来错得很离谱
<div class="progress-bar" ref="progressBar" @click="progressClick">
<div class="bar-inner">
<div class="progress" ref="progress"></div>
<div class="progress-btn-wrapper" ref="progressBtnWrapper"
@touchstart="onTouchStart"
@touchmove="onTouchMove"
@touchend="onTouchEnd"
>
<div class="progress-btn"></div>
</div>
</div>
</div>
progressClick (e) {
// offsetX/y 能反应在当前dom上点击的左边,x轴,所以x轴的值就是 要移动到的宽度
// let progressWidth = Math.min(e.offsetX, this._getProgressBarWidth())
// 修复在点击到小圆点按钮上获取到的偏移量错误的bug
// 获取点击在页面中的x轴偏移量,然后再减去进度条距离左边的间距
let rect = this.$refs.progressBar.getBoundingClientRect()
console.log('rect:', rect)
let progressWidth = Math.min(e.pageX - rect.left, this._getProgressBarWidth())
this._setProgressWidth(progressWidth)
console.log(progressWidth)
this._triggerPercent()
}
我打印了下获取到的rect 信息如下,就是这个dom元素的三围,加上距离左边右边等的信息:这个方法返回一个矩形对象,包含四个属性:left、top、right和bottom。分别表示元素各边与页面上边和左边
rect = {
bottom : 567,
height:30,
left:67.5,
right:307.5,
top:537,
width:240
}
5. 功能优化 - 详情页的随机播放按钮
这里饿随机播放按钮功能就很好做了。在以前的基础上进行修改
src/components/music-list/music-list.vue 这个功能在list组件里
// 给按钮绑定随机播放事件
// 拿到当前的歌曲列表然后调用 action去提交全局状态
random () {
this.randomPlay(this.songs)
}
vuex
/**
* 随机播放歌曲
* @param commit
* @param state
* @param list
*/
export const randomPlay = function ({commit, state}, list) {
// 与顺序播放不同的是,播放模式 和 播放列表不同
commit(types.SET_SEQUENCE_LIST, list)
commit(types.SET_PLAYLIST, shuffle(list))
commit(types.SET_CURRENT_INDEX, 0) // 播放列表打乱之后,直接播放列表中的第一首歌曲就是
commit(types.SET_MODE, playMode.random)
commit(types.SET_FULL_SCREEN, true)
commit(types.SET_PLAYING, true)
}
这里注意的是: 在之前我就觉得 shuffle 的功能有问题,这里发现了,修改了原来的播放列表。下面是修复代码
主要使用了 js 数组的 arr.slice() 方法返回一个新的副本
export function shuffle (arr) {
let _arr = arr.slice() // 返回一个新的数组,包含从 start 到 end (不包括该元素)的 arrayObject 中的元素。
let length = _arr.length
for (let i = 0; i < length; i++) {
let j = getRandomInt(0, i)
let a1 = _arr[j]
_arr[j] = _arr[i]
_arr[i] = a1
}
return _arr
}
上面这个随机播放的功能是做好了。
但是又发现一个问题,切换播放模式的时候,当前播放的歌曲跟着变化了。这个是hi之前的代码
playModelChange () {
let mode = (this.mode + 1) % 3 // 循环3次切换
this.setPlayModel(mode)
let list = null
if (mode === playMode.random) {
// 获得原始列表,打乱顺序后,再赋值给播放列表
list = shuffle(this.sequenceList)
} else {
list = this.sequenceList
}
this.setPlayList(list)
this._resetCurrentIndex(list)
},
---------------------
问题就出在这里的两行代码的顺序:
this.setPlayList(list)
this._resetCurrentIndex(list)
在src/store/getters.js中
export const currentSong = (state) => {
return state.playlist[state.currentIndex] || {}
}
当前歌曲的详细信息是通过 索引和 播放列表获取的。上面的代码先更改了播放列表,引发了变更。导致getters返回的值变化了。
但是,把顺序交换一下,先设置index,为什么不会引发变更呢?
6. 功能优化 - 详情页的 点击歌曲播放 与 播放器模式相冲突
之前的 点击歌曲列表中的歌曲,会直接播放,是没有考虑到 播放模式,现在有播放模式了,功能肯定不正常了。
假如现在是 随机播放模式,再点击列表中的歌曲,会调用以前的顺序播放的代码
export const selectPlay = function ({commit, state}, {list, index}) {
// 先暂时播放列表和排序列表一致
// 且 打开全屏播放器模式,并且设置为播放状态
commit(types.SET_SEQUENCE_LIST, list)
commit(types.SET_PLAYLIST, list)
commit(types.SET_CURRENT_INDEX, index)
commit(types.SET_FULL_SCREEN, true)
commit(types.SET_PLAYING, true)
}
所以更改代码如下
export const selectPlay = function ({commit, state}, {list, index}) {
// 先暂时播放列表和排序列表一致
// 且 打开全屏播放器模式,并且设置为播放状态
commit(types.SET_SEQUENCE_LIST, list)
let playList = null
let _index = index
if (state.mode === playMode.random) {
playList = shuffle(list)
_index = findIndex(playList, list[index]) // 获取点击的歌曲信息,所在新的播放列表中的索引
} else {
playList = list
}
commit(types.SET_PLAYLIST, playList)
commit(types.SET_CURRENT_INDEX, _index)
commit(types.SET_FULL_SCREEN, true)
commit(types.SET_PLAYING, true)
}
function findIndex (list, currentSong) {
let songId = currentSong.id
let index = list.findIndex((item) => {
return item.id === songId
})
return index
}
原理:
- 获取当前点击歌曲在打乱后的列表中的 索引
- 和之前播放模式切换的时候,先提交索引,再提交列表就ok了