共计 2367 个字符,预计需要花费 6 分钟才能阅读完成。
序
决定开始写博客了,从简略的货色写起吧。
明天仍然在推敲各平台音乐的实现细节,发现它们在播放和暂停歌曲的时候,都会有一个短暂的渐入渐出成果,上面就来入手实现一下。
设计
目前想到的较好的方法是在接到用户的播放申请时,通过在短时间内间断缩小音量直至为 0 时再调用 audio 元素的 pause 接口,而接到用户的暂停申请时则首先调用 audio 元素的 play 接口,并在短时间内减少音量至用户设置的音量
想到须要间断进行某个步骤,脑子里就浮现出两种办法,setTimeOut
或 setInterVal
和requestAnimationFrame
,因为没有太耗时的继续异步操作,所以不须要用 setTimeOut
来防止 setInterVal
的累积效应(如果真的有这些操作,应该防止在主线程执行才对)。
咱们心愿音频的渐入渐出可能足够的顺滑,不要产生因为音量变动距离太大而产生声音的颗粒感,只管应用 setInterVal
时回调执行的距离是不固定的,只当执行栈为空时才轮到异步队列的工作执行,然而目前并没有什么工作会妨碍回调函数执行,而 requestAnimationFrame
让回调函数随着屏幕的刷新而调用,使得音量变动的频率变得固定了,看起来如同能更顺滑一点(用它来作非动画渲染工作真的适合吗?)。纸上得来终觉浅,决定两者都实现而后比照一下成果
实现
先来看看有淡入淡出和没有淡入淡出的差异,点这里看示例
上面给出在 requestAnimationFrame
下的实现,首先在 Vue 结构选项内申明须要用到的 data
data () {
return {
...
volume: 1, // 用户设置的歌曲音量
fadeVolume: 0, // 歌曲以后的音量,与 volume 不同的是,该变量是记录歌曲在淡入淡出时的实时音量
volumeFadeInFlag: null, // 保留歌曲淡入的计时器
volumeFadeOutFlag: null, // 保留歌曲淡出的计时器
...
}
}
之后通过循环不断减少或缩小 fadeVolume
的值,直至它为 volume
或 0,比方淡出的代码为
songVolumeFadeOut (audio) {this.volumeFadeInFlag && cancelAnimationFrame(this.volumeFadeInFlag)
const _this = this;
(function fadeOut () {if (_this.fadeVolume - 0.02 <= 0) {
_this.fadeVolume = 0
audio.volume = _this.fadeVolume
cancelAnimationFrame(_this.volumeFadeOutFlag)
_this.volumeFadeOutFlag = null
audio.pause()} else {
_this.fadeVolume = _this.fadeVolume - 0.02
_this.fadeVolume = Number(_this.fadeVolume.toFixed(2))
audio.volume = _this.fadeVolume
_this.volumeFadeOutFlag = requestAnimationFrame(fadeOut)
}
})()}
留神在执行淡出循环前须要先革除淡入循环,如果不这样做的话如果用户猛点暂停和播放按钮,就会同时存在多个淡入淡出,并且它们都无奈达到终止条件,从而陷入死循环中,因为刷新频率是一秒大概 60 次,设置增量为 0.02,就能够在大概一秒内完结淡入和淡出。
写好了淡出淡入代码后,就要寻找执行机会了,我把淡入放在 audio
元素 play
事件的回调函数中,因而淡入代码不须要手动调用 audio.play()
,把淡出放在示意音频播放状态的playing
变量的监听中,当收到音频暂停的申请时执行淡出。
onPlay() {
...
// 在窗口最小化或被暗藏时,requestAnimationFrame 将会推延到窗口复原后执行,这不是咱们想要的
if (document.visibilityState == 'visible') {this.songVolumeFadeIn(audio)
} else {
// 因而在窗口不可见时咱们应用 setInterval 来代替 requestAnimationFrame
this.songVolumeFadeInByInterval(audio)
}
...
}
...
watch: {playing() {
...
if (document.visibilityState == 'visible') {this.songVolumeFadeOut(audio)
} else {this.songVolumeFadeOutByInterval(audio)
}
...
}
}
后面说到 requestAnimationFrame
是专为动画设计的 API
为了进步性能和电池寿命,因而在大多数浏览器里,当
requestAnimationFrame()
运行在后盾标签页或者暗藏的<iframe>
里时,requestAnimationFrame()
会被暂停调用以晋升性能和电池寿命。
如果在一般网页中应用 requestAnimationFrame
来实现音频淡入淡出成果,当然没问题(假如用户没有极快的手速点击播放按钮后霎时切标签或最小化浏览器),然而在理论的桌面播放器中,不止一个主界面能够管制音频的播放和暂停,mini 窗口以及托盘菜单等都能管制,此时如果主界面被最小化,则不会进行 requestAnimationFrame
循环,因而在这里应用了 document.visibilityState 来判断主界面是否可见,据此来抉择到底是应用 requestAnimationFrame
还是setInterval
。
点这里查看简略版源代码