本文目录
- 1.全屏播放器动画
- 2.配置vuex
- 3.歌曲的播放与模式转换
- 4.进度条
- 5.歌词的滚动效果
- 6.删除歌曲
1.全屏播放器动画
项目的所有项目下默认都会有一个默认的mini播放器,同时也会有一个专门的全屏播放器也页面,这两个播放器显示和隐藏是相反的,所以我们在新建的player组件中(我们把player组件直接引入到App.vue中),可以在data中定义一个fullScreen变量(默认值为true),然后对两个播放器的div进行绑定
<div>
<div v-show="!fullScreen" class="mini-player"></div>
<div v-show="fullScreen" class="player"></div>
</div>
两个播放器的z-index都不小于前面所说的page样式,否则会显示不出来,可以也设置成9999
当mini播放器和全屏播放器切换的时候,全屏播放器会有一个透明度从0到1的过渡动画,同时全屏播放器的头部区域player-header有一个从上往下的过渡以及底部player-operate有一个从下往上的过渡。
把全屏播放器player包裹在<transition name="player">标签中,然后添加上样式代码就可以实现效果了
.player-enter-active,
.player-leave-active {
transition: all 0.3s;
opacity: 1;
.player-header,
.player-operate {
transform: translate3d(0, 0, 0);
transition: all 0.3s cubic-bezier(0.86, 0.18, 0.82, 1.32);
}
}
.player-enter,
.player-leave-to {
opacity: 0;
.player-header {
transform: translate3d(0, -100px, 0);
}
.player-operate {
transform: translate3d(0, 100px, 0);
}
}
2.配置vuex
整个项目的很多个地方都可以控制播放器,很显然这需要大量的跨组件通讯,所以需要配置vuex进行状态管理
vue-cli创建目录文件中本来就知道store.js,但是state、mutations等都是集合在一起的,我们需要新建一个store文件夹,去进行进一步的细分和管理。
新建actions.js
const actions = {}
export default actions
getters.js
const getters= {}
export default getters
mutations.js
const mutations= {}
export default mutations
state.js
const state= {}
export default state
index.js
import Vue from 'vue'
import Vuex from 'vuex'
import state from './state'
import mutations from './mutations'
import getters from './getters'
import actions from './actions'
Vue.use(Vuex)
const store = new Vuex.Store({
state,
mutations,
getters,
actions
})
export default store
最后修改一下项目入口文件main.js的store引入路径就完成了vuex的配置了
import store from './store/index';
3.歌曲的播放与模式转换
使用示例:歌曲的循环模式mode,mode的值我们可以设置的更加语义化,同时把这个值的设置单独写出来,写在common文件夹的js文件夹中的aliasConfig.js中
export const playMode = {
sequence:0,
loop:1,
random:2
}
在state中引用playMode并且定义mode
import { playMode } from '../../common/js/aliasConfig'
const state = {
mode:playMode.sequence,
}
export default state
在mutations.js中定义改变mode的方法
SET_MODE(state, val) {
state.mode = val
}
在getters.js中去动态计算并返回这个值
mode(state) {
return state.mode
}
我们在要改变这个mode值的页面中先使用语法糖引用mutations
import { mapMutations } from 'vuex'
然后就可以将mutations中的方法映射到methods属性中
methods: {
...mapMutations([
'SET_MODE'
]),
addToPlay(item,index){
this.SET_MODE(1)
}
}
在刚开始拿到歌曲信息的时候,主要都是些歌曲的基本信息,歌曲的播放链接和歌词需要根据歌曲信息的id值去发送对应的请求获得。
在拿到歌曲的链接之后我们就可以把链接赋值给页面中的audio标签
<audio :src="musicData.url" ref="audio" @timeupdate="updataTime" @ended="end"></audio>
通过togglePlay事件去控制音乐的播放与暂停
togglePlay(val) {
if (!this.currentSong) return;
if (val === true || val === false) {
this.playing = val;
} else {
this.playing = !this.playing;
}
const audio = this.$refs.audio;
if (this.playing) {
audio.play();
} else {
audio.pause();
}
}
上一首下一首功能的实现是通过改变currentSongIndex的值来实现的,同时监听了currentSongIndex的变化,当currentSongIndex变化的时候,会触发请求歌曲信息以及歌曲资源等事件。
切换播放模式,尤其是随机模式,打乱的是备份出来的播放列表,而且注意打乱之后,当前播放歌曲的index也需要进行更新
const newIndex = newPlayList.findIndex(
item => item.id === this.currentSong.id
);
打乱播放列表的函数:
getRandomList(arr) {
const newArr = [].concat(arr);
return newArr.sort((a, b) => (Math.random() > 0.5 ? -1 : 1));
}
三种播放模式的样式判断(在计算属性computed中)
modeIcon() {
return this.mode === playMode.sequence
? "icon-liebiaoxunhuan"
: this.mode === playMode.loop
? "icon-danquxunhuan"
: "icon-suiji";
}
把其绑定在启动的标签上
<i class="iconfont ft-40" :class="modeIcon"></i>
4.进度条
audio标签自带timeupdate事件,当歌曲播放的时候会去自动触发
@timeupdate="updataTime"
updataTime(e) {
if (!this.touchbarWillMove) {
this.currentTime = e.target.currentTime;
this.overTime = e.target.duration;
}
// 歌词滚动
if (this.lyricData) {
this.moveLyric();
}
}
计算已播放的百分比(在计算属性computed中)
barPercent() {
let per = this.currentTime / this.overTime;
if (per === 0) {
return 0;
}
//四舍五入
per = Number(per * 100).toFixed();
return `${per}%`;
}
这个百分比我们可以动态的赋值给进度条的width和小圆点的left
:style="{width:
${barPercent}}"
实现小圆点的拖动功能
<div
class="bar-btn"
:style="{left:`${barPercent}`}"
@touchmove.prevent="progressMove"
@touchend="progressEnd"
></div>
手指按着小圆点移动的时候触发touchmove=>progressMove
结束的时候触发touchend=>progressEnd
progressMove(e) {
const pageX = e.touches[0].pageX;
this.calcPercent(pageX);
},
calcPercent(x) {
const offsetLeft = this.$refs.progressBar.offsetLeft;
const barWidth = this.$refs.progressBar.clientWidth;
// 拖动点相当于进度条所占的宽度
let moveWidth = x - offsetLeft;
if (moveWidth > barWidth) moveWidth = barWidth;
if (moveWidth < 0) moveWidth = 0;
let p = moveWidth / barWidth;
this.currentTime = this.overTime * p;
},
progressEnd() {
this.resetPlayer();
},
resetPlayer() {
this.$refs.audio.currentTime = this.currentTime;
this.togglePlay(true);
},
5.歌词的滚动效果
一开始拿到的初始歌词数据是一整个字符串,每一句歌词以\n
分割
所以首先把其转换成数组,然后将每一行歌词的时间和真正的歌词分割出来,存放到真正可以使用的歌词信息数组lyricLines
initLines() {
this.lyricLines = [];
if (this.lyricData) {
const lines = this.lyricData.split("\n");
for (let i = 0; i < lines.length; i++) {
// 拿到某一行的值
const line = lines[i];
// 时间
const timeExp = /\[(\d{2}):(\d{2}\.\d{2,3})\]/g;
// 匹配到每一行的时间
const result = timeExp.exec(line);
if (result) {
// 计算出来每一行歌词的时间time
const time =
Number(result[1] * 60 * 1000) +
Number(result[2] * 1000);
const txt = line.replace(timeExp, "").trim();
this.lyricLines.push({
time,
txt
});
}
}
}
},
把lyricLines渲染到歌词页面上并放置到scroll中,就可以静态展示歌词信息了,接下来实现歌词的动态滚动
moveLyric() {
this.currentLineNumber = this.findCurrentNumber(
this.currentTime * 1000
);
// 当歌词高亮在超过第六行的时候才进行滚动,这样可以让高亮行在页面中间
if (this.currentLineNumber > 6) {
// scrollToElement是写在scroll组件里的方法
// scrollTo也是写在组件里的,在本页面中引用后就可以直接使用
this.$refs.lyricScroll.scrollToElement(
this.$refs.lyricLine[this.currentLineNumber - 6],
1000
);
} else {
// 当高亮行小于6行的时候
this.$refs.lyricScroll.scrollTo(0, 0, 1000);
}
},
findCurrentNumber(time) {
// 返回第一个被匹配到的歌词的下标
for (let i = 0; i < this.lyricLines.length; i++) {
if (time < this.lyricLines[i].time) {
return i - 1;
}
}
// 当当前播放时间大于所有歌词的时间时,默认返回最后一行歌词的下标
return this.lyricLines.length - 1;
},
这个动态滚动歌词的方法应该在歌词播放时间变化的时候去触发,并且在拖到进度条改变播放时间的时候触发。
6.删除歌曲
根据拿到的id值去找出播放列表中符合的那首歌曲,然后删除掉
DEL_FROM_PLAY_LIST(state, val) {
const index = state.playList.findIndex(item => item.id === val.delSong.id)
state.playList.splice(index, 1)
// 当删除的歌曲不是当前的歌曲的时候,
// 根据传过来的当前播放歌曲的curSong.id,去查找更新后的歌曲列表playList
// 拿到更新后的当前播放歌曲的新的currentIndex
if (val.delSong.id !== val.curSong.id) {
state.currentIndex = state.playList.findIndex(item => item.id === val.curSong.id)
}
},
网友评论