接近年尾了,作为一个电子商城自然是要跟风赏给用户一个年度账单的,临时要求加个背景音乐功能,刚接到任务时,直观觉得这个应该很简单吧,但是却在自动播放中踩了坑。
audio 标签
直接使用 HTML5 新增的 audio 标签是最简单的方式了,自动播放自然就想到了 autoplay 属性,但是这个属性会导致一个莫名其妙的问题就是,使用 pause 函数无法暂停音乐。
既然不能使用 autoplay 属性,那我就用代码手动 play 咯,结果 PC 端 chrome 浏览器直接报错,内容如下
play 函数必须在用户和文档有交互后才可以正常调用
至于为啥会有这个限制,主要是出于用户角度考虑,怕你声音突然出来,吓着用户了。更多可以看这个issue #178
在移动端表现有些不一致,在 android 浏览器上,不存在这个问题,可以正常自动播放,但是 iOS 却不行,因此抢救办法就是在用户首次和文档交互时,比如 touchstart 事件时调用一次 play。
后续:在尝试用 AudioContext 时,chrome 控制台会有个提示,大概内容如下(具体忘得差不多的)
关于 audio autoplay 的策略在回 chrome 70 重新启用
避免误导,找到了具体报错信息
- chrome 提示:DOMException: play() failed because the user didn’t interact with the document first.
- Safari 提示:NotAllowedError: The request is not allowed by the user agent or the platform in the current context, possibly because the user denied permission.
具体原因貌似是因为,开发者不满意这个限制,会采取各种奇葩的 hack 方式去绕这个限制,反而导致应用性能下降。
AudioContext 对象
由于 audio 在 iOS 上不能自动播放的限制,难道就这么算了,运营那帮人才不管你呢,要的就是自动播放怎么办?因此我还是小小的挣扎了一下,使用了一下这个据说更灵活的 AudioContext 对象。
简单的代码如下
export function loadAudioFile(url) {
return new Promise((resolve, reject) => {
var xhr = new XMLHttpRequest()
xhr.open('GET', url, true)
xhr.responseType = 'arraybuffer';
xhr.onload = function (e) {
resolve(xhr.response)
};
xhr.abort = xhr.timeout = xhr.error = function(e) {
reject(e)
}
xhr.send()
})
}
const audioPath = ''
loadAudioFile(audioPath).then(res => {
this.context = new (window.AudioContext || window.webkitAudioContext)()
this.context.decodeAudioData(res, audioBuffer => {
this.audioBuffer = audioBuffer
this.playSound()
})
})
playSound() {
this.audioSource = this.context.createBufferSource();
this.audioSource.buffer = this.audioBuffer;
this.audioSource.loop = true;
this.audioSource.connect(this.context.destination);
this.audioSource.start(this.playResume)
this.playStart = new Date().getTime() - this.playResume * 1000;
this.playing = true
},
pauseSound() {
this.playResume = new Date().getTime();
this.playResume -= this.playStart;
this.playResume /= 1000;
this.playResume %= this.audioSource.buffer.duration;
this.audioSource.stop()
this.playing = false
}
在 android 测试没有啥问题,满心欢喜跑去 iOS 上测试,却没有任何反应,这是为啥呢,第一个就是 iOS 上没有 AudioContext 对象,只有 webkitAudioContext 对象,因此这个存在性判断必不可少,第二个问题,保存内容是没有获得用户权限,但我估摸着和 chrome 上报错必须和用户有交互差不多,因为我手动开启是没问题的,折腾到这里,并没有卵用,可怕。
在这里有个问题有必要提一下,start 方式只能调用一次,因此如果需要暂停与继续,比如多次调用 createBufferSource 创建 source 对象。