Better

Ethan的博客,欢迎访问交流

audio 与 AudioContext 踩坑

接近年尾了,作为一个电子商城自然是要跟风赏给用户一个年度账单的,临时要求加个背景音乐功能,刚接到任务时,直观觉得这个应该很简单吧,但是却在自动播放中踩了坑。

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 对象。

参考



留言