使用 AudioContext restart 音频

Posted 码小余の博客

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了使用 AudioContext restart 音频相关的知识,希望对你有一定的参考价值。

使用 AudioContext restart 音频

📌 前言

为什么是 restart 呢,因为我遇到的问题是点击播放后无论如何再次点击播放,它就报错

(index):41 Uncaught DOMException: Failed to execute ‘start’ on ‘AudioBufferSourceNode’: cannot call start more than once.
at htmlButtonElement.

📌 问题复现

我在页面上定义了两个按钮(一个播放按钮,一个暂停按钮)和一个选择文件的 input ,我想要实现的功能是点击播放就能播放,点击暂停就能暂停,所以呢我会定义了两个点击事件(播放和暂停)

接下来使用 [audioContext](AudioContext - Web APIs | MDN (mozilla.org)) 来实现音乐播放,为了兼容各个浏览器,我首先写了各个浏览器获取 audioContext 的方法

var audioCtx = new (window.AudioContext || window.webkitAudioContext)();

然后创建 [createBufferSource()](AudioContext.createBufferSource() - Web API 接口参考 | MDN (mozilla.org)) 对象对音频播放这个功能来说是必须的,于是就有了下面的错误代码,我紧接着就在 audioCtx 变量后面新建了 createBufferSource 变量

var AudioBufferSourceNode = audioCtx.createBufferSource()

var play = document.getElementById("play")
var pause = document.getElementById("pause")
var bufferData = null

注意:上面的 AudioBufferSourceNode 是这篇文章所述的问题所在

然后是监听 input 上的 change 事件,当用户选择音频文件后执行回调函数中的代码

document.getElementById("loadfile").addEventListener("change", function () { 
    var file = this.files[0] // 获取音频文件对象
	// ... 其他代码
}

然后在拿到音频文件对象后创建 [FileReader](FileReader - Web API 接口参考 | MDN (mozilla.org)) 对象来异步读取存储在用户计算机上的文件(或原始数据缓冲区)的内容,然后监听其 load 事件,当音频文件加载完毕后使用 decodeAudioData 对其进行解码操作,decodeAudioData 中有两个参数,第一个是 audioData音频数据;第二个是一个回调函数,它返回一个 buffer 缓存,当拿到成功 buffer 后就说明音频加载完毕了,现在就可以点击播放和暂停了

document.getElementById("loadfile").addEventListener("change", function () {  
    var file = this.files[0]
    var fr = new FileReader()

    fr.addEventListener("load", function(e) {
        audioCtx.decodeAudioData(e.target.result, function(buffer){
            // playFun(buffer);  // 解码后返回的AudioBuffer对象作为播放函数的参数传入
            console.log("音乐载入完毕");
            bufferData = buffer

            /* 写到 decodeAudioData 事件内部,当音乐加载完毕后才能执行播放和暂停 */
            // 播放
            play.addEventListener("click", function () {  
                console.log("播放");
                AudioBufferSourceNode.buffer = bufferData // AudioBuffer数据赋值给buffer属性
                AudioBufferSourceNode.connect(audioCtx.destination); // 如果只是播放音频,这边就直接将AudioBufferSourceNode连接到AudioDestinationNode

                AudioBufferSourceNode.start(0); // 开始播放音频
                console.log("音乐状态:", audioCtx.state);
            })

            // 暂停
            pause.addEventListener("click", function () {  
                console.log("暂停");
                AudioBufferSourceNode.stop(0) // 停止播放音乐
                console.log("音乐状态:", audioCtx.state);
            })
        },function(err){
            console.log(err);
        })
    })
    fr.readAsArrayBuffer(file);
})

注意:以上代码是bug代码,请谨慎复制进行使用

📌 问题解决

在我查阅大部分资料之后,同样是没有找到合适的答案,但是在寻找答案的同时也给我带来了灵感,那就是如果重复点击播放的话,而当前的 AudioBufferSourceNode 还是用的旧的(没有更新或重新赋值),则会出现本文提到的那个错误,或找不到 buffer 之类的红色错误,所以说,我应该在点击播放时为 AudioBufferSourceNode 重新赋值

一起来实现吧~

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta http-equiv="X-UA-Compatible" content="IE=edge">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Document</title>
</head>
<body>
  <div>
    <button id="play">播放</button>
    <button id="pause">暂停</button>
    <input type="file" id="loadfile">
  </div>

  <script>
    var audioCtx = new (window.AudioContext || window.webkitAudioContext)();
    var AudioBufferSourceNode = null
    var play = document.getElementById("play")
    var pause = document.getElementById("pause")
    var bufferData = null

    document.getElementById("loadfile").addEventListener("change", function () {  
      var file = this.files[0]
      var fr = new FileReader()

      fr.addEventListener("load", function(e) {
        audioCtx.decodeAudioData(e.target.result, function(buffer){
            // playFun(buffer);  // 解码后返回的AudioBuffer对象作为播放函数的参数传入
            console.log("音乐载入完毕");
            bufferData = buffer

            /* 写到 decodeAudioData 事件内部,当音乐加载完毕后才能执行播放和暂停 */
            // 播放
            play.addEventListener("click", function () {  
              console.log("播放");
              AudioBufferSourceNode = audioCtx.createBufferSource() // 必须在播放时重新创建 AudioBufferSourceNode 对象,否则会出现不能再次播放的问题
              AudioBufferSourceNode.buffer = bufferData // AudioBuffer数据赋值给buffer属性
              AudioBufferSourceNode.connect(audioCtx.destination); // 如果只是播放音频,这边就直接将AudioBufferSourceNode连接到AudioDestinationNode
              
              AudioBufferSourceNode.start(0); // 开始播放音频
              console.log("音乐状态:", audioCtx.state);
            })
            
            // 暂停
            pause.addEventListener("click", function () {  
              console.log("暂停");
              AudioBufferSourceNode.stop(0) // 停止播放音乐
              console.log("音乐状态:", audioCtx.state);
            })
        },function(err){
            console.log(err);
        })
      })
      fr.readAsArrayBuffer(file);
    })

    
  </script>
</body>
</html>

📌 效果图

以上是关于使用 AudioContext restart 音频的主要内容,如果未能解决你的问题,请参考以下文章

Safari 上的 AudioContext 问题

在单元测试中使用 AudioContext

使用来自音频标签的源使用 AudioContext

音频与 AudioContext

Safari 上的 AudioContext

如何控制(音频缓冲区)AudioContext() 的音量?