vue-music (12) 播放内核
程序员文章站
2022-03-07 17:18:48
...
最重要的就是这个播放器播放页面
从整体来看,点击list列表的歌曲会储存到vuex中,然后进入play页面,首先给进入来一个动画
1.手写动画的模板
<transition name="normal" @enter="enter" @after-enter="afterEnter" @leave="leave" @after-leave="afterLeave">
这是自定义动画,需要手写
_getPosAndScale () { // 基础配置,// 算出从左下角到最终地点的偏移量,和大小比例 起点到终点 x y也可以自定义,只不过就不是从左下角开始罢了
const targetWidth = 40
const paddingLeft = 40
const paddingBottom = 30
const paddingTop = 80
const width = window.innerWidth * 0.8 // window.innerWidth窗口的文档显示区的宽度,以像素计
const scale = targetWidth / width
const x = -(window.innerWidth / 2 - paddingLeft) // 从左下角开始偏移,所以是负值
const y = window.innerHeight - paddingTop - width / 2 - paddingBottom
return {
x,
y,
scale
}
}
enter(el, done){ // done是回调函数 即执行下一个函数
const {x, y, scale} = this.__getPosAndScale()
let animation = {
0: {
transform: `translate3d(${x}px, ${y}px, 0) scale(${scale})`
},
60: {
transform: `translate3d(0, 0, 0) scale(1.2)` // 放大
},
100: {
transform: `translate3d(0, 0, 0) scale(1.0)` // 回复原来大小
}
}
// 注册动画
animations.registerAnimation({
name: 'move',
animation, // 自定义得到动画
// 参数设置
presets: {
duration: 1000, // 持续时间
easing: 'linear', // 过渡效果
delay: 50 // 延时
}
}
// 执行动画
animations.runAnimation(this.$refs.要发生动画的部分,'move', () => {
done() // 执行后续函数
})
},
afterEnter () {
// 取消动画
animations.unregisterAnimation('move')
this.$refs.cdWrapper.style.animation = ''
},
leave (el, done) {
this.$refs.cdWrapper.style.transition = 'all 0.4s'
const {x, y, scale} = this._getPosAndScale()
this.$refs.cdWrapper.style[transform] = `translate3d(${x}px, ${y}px, 0) scale(${scale})`
// this.$refs.cdWrapper.addEventListener('transformend', done)
// 执行动画
this.$refs.cdWrapper.addEventListener('transitionend', () => { //动画执行完毕之后
done()
})
},
afterLeave () {
//取消动画
this.$refs.cdWrapper.style.transition = ''
this.$refs.cdWrapper.style[transform] = ''
},
cdwrapper中的图片随着播放而旋转是通过设置‘play’和’pouse’属性得到
而这两个属性也是动态计算得到
<div class="cd" :class="cdCls" >
<img :src="currentSong.imageurl" alt="" class="image">
</div>
computed:{
cdCls () {
return this.playing ? 'play' : 'play pause' // this.playing 状态是根据vuex得到,全组件通用
},
}
...
&.play {
animation: rotate 20s linear infinite;
}
&.pause {
animation-play-state: paused;
}
音频播放
<audio ref="audio" :src="currentSong.url" @play="ready" @error="error" @timeupdate="timeUpdate" @ended="songEnd"></audio>
ready () {
this.songReady = true
this.savePlayHistory(this.currentSong)
},
error () {
this.songReady = true
},
timeUpdate (e) {
this.currentTime = e.target.currentTime // 动态获得播放当前时间
},
songEnd () { // 判定是单曲循环还是播放下一曲
if (this.mode === playMode.loop) {
this.loop()
} else {
this.next()
}
},
自带几个方法
aaa@qq.com:能不能进行播放
aaa@qq.com:不能播放时反馈
aaa@qq.com:实时更新播放进度
aaa@qq.com:播放结束后
play():audio自带方法 播放
pause(): audio自带方法 暂停
下一曲:
next () {
if (!this.songReady) { // songReady是标志位,当歌曲能播放时,默认false,只有error返回为能播放时,变为ture,进而return跳过,执行下面的函数
return
} else {
if (this.playList.length === 1) { // 歌曲列表只有一首歌,单曲循环
this.loop()
return
}
let index = this.currentIndex + 1 // 因为是下一首,所以所引致加一
if (index === this.playList.length) { // 最后一首歌, 返回到第一首
index = 0
}
this.setCurrentIndex(index) // actions的方法,根据index决定播放的具体是哪首
if (!this.playing) { // 调整播放状态
this.togglePlaying()
}
}
this.songReady = false // 改回标志位状态
},
上一曲
prev () {
if (!this.songReady) {
return
}
if (this.playList.length === 1) {
this.loop()
return
} else {
let index = this.currentIndex - 1 // 上一首, 索引值减一
if (index === -1) { // 第一首 的前一曲
index = this.playList.length - 1 // 最后一曲
}
this.setCurrentIndex(index)
if (!this.playing) {
this.togglePlaying()
}
}
this.songReady = false
},
单曲循环
loop () { // 循环播放
this.$refs.audio.currentTime = 0 // 显示时间跳回到0秒
this.$refs.audio.play() // audio自带方法
if (this.currentLyric) {
this.currentLyric.seek(0) // 歌词 跳转到开头部分
}
},
歌词
<scroll class="middle-r" ref="lyricList" :data="currentLyric && currentLyric.lines">
<div class="lyric-wrapper">
<div v-if="currentLyric">
<p class="text" :class="{'current': currentLine === index}" v-for="(line, index) in currentLyric.lines" :key="line.id" ref="lyricLine">
{{line.txt}}
</p>
</div>
</div>
</scroll>
getLyric () {
this.currentSong.getLyric().then((lyric) => { // 歌词不符,跳过
if (this.currentSong.lyric !== lyric) {
return
}
this.currentLyric = new Lyric(lyric, this.handelLyric) // 创建实例
if (this.playing) {
this.currentLyric.play() // 自带方法,歌词播放
}
// console.log(this.currentLyric)
}).catch(() => { // 寻不到歌词时候 的错误处理
this.currentLyric = null
this.playingLyric = ''
this.currentLine = 0
})
},
handelLyric ({lineNum, txt}) { // 处理一下歌词,保证当前播放歌词始终出现在屏幕中间
this.currentLine = lineNum
if (lineNum > 5) {
let lineEl = this.$refs.lyricLine[lineNum - 5]
this.$refs.lyricList.scrollToElement(lineEl, 1000)
} else {
this.$refs.lyricList.scrollTo(0, 0, 1000)
}
this.playingLyric = txt
},
横滑中间区域,显示/隐藏歌词、
<div class="middle" @touchstart.prevent="middleTouchStart" @touchmove.prevent="middleTouchMove" @touchend.prevent="middleTouchEnd">
created () {
this.touch = {} // 先创建一个touch事件
},
middleTouchStart (e) {
this.touch.initiated = true
const touch = e.touches[0]
this.touch.startX = touch.pageX // 记录开始触摸的x, y
this.touch.startY = touch.pageY
},
middleTouchMove (e) {
if (!this.touch.initiated) {
return
}
const touch = e.touches[0]
const deltaX = touch.pageX - this.touch.startX // 计算出x,y偏移量
const deltaY = touch.pageY - this.touch.startY
if (Math.abs(deltaY) > Math.abs(deltaX)) { // 若y偏移量大,说明是下滑,不是左滑
return
}
const left = this.currentShow === 'cd' ? 0 : -window.innerWidth // 确定当前页面是哪个,并由此得出left值
const offsetWidth = Math.min(0, Math.max(-window.innerWidth, left + deltaX))
this.touch.percent = Math.abs(offsetWidth / window.innerWidth)
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') {
if (this.touch.percent > 0.1) { // 从有往左滑动
offsetWidth = -window.innerWidth
opacity = 0 // 透明
this.currentShow = 'lyric'
} else {
offsetWidth = 0
opacity = 1
}
} else {
if (this.touch.percent < 0.9) { // 从左往右滑动
offsetWidth = 0
opacity = 1
this.currentShow = 'cd'
} else {
offsetWidth = -window.innerWidth
opacity = 0
}
}
const time = 300
this.touch.percent = Math.abs(offsetWidth / window.innerWidth)
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` // 动画
},
监听事件
watch: {
currentSong (newsong, oldsong) { // 当前播放的状态
if (!newsong.id) { // 没有播放
return
}
if (newsong.id === oldsong.id) { // 播放的是同一首
return
}
if (this.currentLyric) {
this.currentLyric.stop()
}
clearTimeout(this.timer)
this.timer = setTimeout(() => {
this.$refs.audio.play() // 观察当前歌曲发生变化就 播放歌曲
this.getLyric() // 获得歌词 渲染dom
}, 1000)
},
playing (newPlaying) {
if (!this.songReady) {
return
}
const audio = this.$refs.audio
// 因为当音乐还没有获取的时候,不能调用play,所以要用$nextick 将回调延迟到下次 DOM 更新循环之后执行。在修改数据之后立即使用它,然后等待 DOM 更新
this.$nextTick(() => { // 异步获取 在currentSong变化的时候,去调用play()方法:
newPlaying ? audio.play() : audio.pause()
})
// this.songReady = false
}
},
获得当前播放时间 进度条设置
timeUpdate (e) {
this.currentTime = e.target.currentTime // audio自带方法 动态获得播放当前时间,但得到的是时间戳
},
formate (interval) { // 转换时间戳为时间格式
interval = interval | 0 // 向下取整 等于 math.floor()
const minute = interval / 60 | 0
const second = this._pad(interval % 60)
return `${minute}:${second}`
},
// 补零函数
_pad (num, n = 2) { // 一位数补0凑两位数函数
let len = num.toString().length // 数字变字符串,得到位数个数
while (len < n) { // 当num为一位数时,进入循环
num = '0' + num
len++ // 跳出条件
}
return num
},
https://blog.csdn.net/weixin_40814356/article/details/80375143
上一篇: 如何优雅的用MarkDown编写一份简历
下一篇: 你知道自己适合做程序员吗?
推荐阅读
-
小米电视5再曝光:12纳米芯片 支持8K播放
-
ubuntu12下交叉编译内核时:“mkimage“ command not found - U-Boot images will not be built
-
36个月不卡!OPPO Find X3系列获推ColorOS 12正式版:安卓12内核
-
Intel 11代、12代酷睿放弃SGX支持 不能播放UHD蓝光了
-
vue-music关于Player播放器组件详解
-
11/12代酷睿无法播放4K蓝光 疑与安全漏洞有关:Intel直接放弃
-
11/12代酷睿+Win11封杀4K蓝光碟 播放软件厂商回应:无能为力
-
尴尬!M1 Pro/Max翻车:有用户反馈用苹果新本播放视频内核崩溃
-
VB.NET并行与分布式编程(6)-线程与内核同步[12]
-
操作系统(李治军) L12内核级线程的实现