web-audio-api可视化音乐播放器,实现暂停切换歌曲功能,粉色系专场~

可视化的音乐播放器,可戳我观看效果

了解Web-Audio-Api

  • 基础知识

<audio>标签是HTML5的新标签,经过添加src属性实现音乐播放。javascript

AudioContext是音频播放环境,原理与canvas的绘制环境相似,都是须要建立环境上下文,经过上下文的调用相关的建立音频节点,控制音频流播放暂停操做等操做,这一些操做都须要发生在这个环境之中。html

try{
    var audioCtx = new (window.AudioContext || window.webkitAudioContext)(); 
}catch(e){
    alert('Web Audio API is not supported in this browser');
}
复制代码

AudioNode接口是一个处理音频的通用模块,它能够是音频音源模块,音频播放设备模块,也能够是中间音频处理模块。不一样的音频节点的链接(经过AudioContext.connect()),以及终点链接AudioContext.destination(能够看做是链接到耳机或扬声器设备)完成后,才能输出音乐。java

常见的音频节点:
AudioBufferSourceNode: 播放和处理音频数据
AnalyserNode: 显示音频时间和频率数据 (经过分析频率数据能够绘制出波形图之类的视图,可视化的主要途径)
GainNode: 音量节点,控制音频的总音量
MediaElementAudioSourceNode: 关联HTMLMediaElement,播放和处理来自<video>和<audio>元素的音频
OscillatorNode: 一个周期性波形,只建立一个音调
...
复制代码
  • 运行模式
  1. 建立音频上下文
  2. 在上下文中,建立音频源
  3. 建立音频节点,处理音频数据并链接
  4. 输出设备

建立音频上下文

try{
    var audioCtx = new (window.AudioContext || window.webkitAudioContext)(); 
}catch(e){
    alert('Web Audio API is not supported in this browser');
}
复制代码

建立音频源

因为音频文件的数据是二进制(非文本),因此要设置请求头的responseTypearraybuffer,将.mp3音频文件转换成数组缓冲区ArrayBuffergit

AudioContext.decodeAudioData解码成功以后获取buffer,执行回调函数,将数据放入AudioBufferSourceNodegithub

方法一采用流式加载音乐文件,简单易懂,缺点是经过createMediaElementSource加载的src文件必须是同源,不容许跨域web

下面步骤主要根据方法2。canvas

  • 方法一:经过HTMLMediaElement流式加载
<audio src="1.mp3"></audio>
  <script>
    let audio = document.querySelector('audio');
    let audioCtx = new (window.AudioContext || window.webkitAudioContext)();
    audio.addEventListener('canplay', function () {
      let source = audioCtx.createMediaElementSource(audio);
      source.connect(audioCtx.destination);
      audio.play()
    })
  </script>
复制代码
  • 方法二:经过XMLHttpRequest获取资源
let xhr = new XMLHttpRequest();
    xhr.open('GET', '1.mp3', true);
    xhr.responseType = 'arraybuffer';
    xhr.onload = function () {
      audioCtx.decodeAudioData(xhr.response, function (buffer) {
        getBufferSuccess(buffer)
      })
    }
复制代码
  • 方法三:经过input file获取
let input = document.querySelector('input');
    input.addEventListener('change', function () {
      if (this.files.length !== 0) {
        let file = this.files[0];
        let fr = new FileReader();
        fr.onload = function () {
          let fileRet = e.target.result;
          audioCtx.decodeAudioData(fileRet, function (buffer) {
            getBufferSuccess(buffer);
          }, function (err) {
            console.log(err)
          })
        }
        fr.readAsArrayBuffer(file);
      }
    })
复制代码

处理音频数据

function getBufferSuccess(buffer) {
      // 建立频率分析节点
      let analyser = audioCtx.createAnalyser();
      // 肯定频域的快速傅里叶变换大小
      analyser.fftSize = 2048;
      // 这个属性可让最后一个分析帧的数据随时间使值之间的过渡更平滑。
      analyser.smoothingTimeConstant = 0.6;
      // 建立播放对象节点
      let source = audioCtx.createBufferSource();
      // 填充音频buffer数据
      source.buffer = buffer;
      // 建立音量节点(若是你须要用调整音量大小的话)
      let gainNode = audioCtx.createGain();
      
      // 链接节点对象
      source.connect(gainNode);
      gainNode.connect(analyser);
      analyser.connect(audioCtx.destination);
    }
复制代码

获取音频频率

  • 方法一:用js的方法获取(经过监听audioprocess事件,因为性能问题,将会被弃用,不作详细说明,感兴趣的能够了解一下)
// 此方法须要补充节点的链接
      let javascriptNode = audioCtx.createScriptProcessor(2048, 1, 1);
      javascriptNode.connect(audioCtx.destination);
      analyser.connect(javascriptNode);
      
        this.javascriptNode.onaudioprocess = function () {
            currData = new Uint8Array(analyser.frequencyBinCount);
            analyser.getByteFrequencyData(currData);
        }

复制代码
  • 方法二:用AnalyserNode获取

获取AnalyserNode节点里的频率长度frequencyBinCount,实例化长度为8位的整型数组,经过AnalyserNode.getByteFrequencyData将节点中的频率数据拷贝到数组中去,值的大小在0 - 256之间,数值越高代表频率越高;AnalyserNode.getByteTimeDomainData原理同样,不过获取的是频率大小,两种方法根据需求选一种便可。api

function getData () {
      // analyser.frequencyBinCount 可视化值的数量,是前面fftSize的一半
      let currData = new Uint8Array(analyser.frequencyBinCount);
      analyser.getByteFrequencyData(currData);
      analyser.getByteTimeDomainData(currData);
    }
复制代码

输出设备

AudioBufferSourceNode.start(n) n表示开始的时间,默认为0,开始播放音频 AudioBufferSourceNode.stop(n) 音频在第n秒时间中止,若没有传值表示当即中止跨域

其余api

AudioContext.resume() 控制音频的播放
AudioContext.suspend() 控制音频的暂停
AudioContext.currentTime 获取当前音频播放时间
AudioBufferSourceNode.buffer.duration 获取音频的播放总时长
GainNode.gain.value 控制音量大小 [0, 1]
GainNode.gain.linearRampToValueAtTime 实现音量的渐入渐出数组

Canvas绘制可视化效果

了解上面的api,就能够来着手绘制啦~,你想绘啥就绘啥,频繁的调用canvas的api很耗性能问题,这里讲下我在测试中提升性能的小技巧。

  • 多分层canvas,一些不须要频繁改动的绘制,例如背景,固定的装饰绘制,能够采用另外一个canvas的上下文来绘制
  • 离屏绘制,原理是生成一个没有出如今页面的canvas,在这个缓存的canvas中绘制,而真正展现的canvas只须要经过drawImage这个api将画面绘制出来便可,参考此博文
  • 固定好lineWidth的长度,而不是每绘制一个就设定一次lineWidth
  • 绘制区域提早计算好,不要让canvas边绘制同时还要计算位置(canvas:好累哦~) 总而言之,少调用canvas api,但是也不要为了提升性能而抛弃你的一些天马星空的想法哦

遇到的问题

在切换歌曲中,遇到了这个报错Failed to set the 'buffer' property on 'AudioBufferSourceNode': Cannot set buffer to non-null after it has been already been set to a non-null buffer at AudioContext,大体是讲AudioBufferSourceNode的buffer属性在以前我已经设置过了,不能被从新设置新的buffer值,因为播放歌曲主要是经过其数组缓冲区ArrayBuffer来进行,可看看issue,解决办法就是当须要切换歌曲状况下,将当前的AudioBufferSourceNode销毁,从新建立上下文环境,音频节点,链接等操做。

源码在这,交互部分写得有点乱,由于当时原来只是想练练可视化,以后想到啥功能就加,因此致使代码看起来冗余繁琐,你们能够参考看看audio实现,主要在MusicPlay对象。

小白第一次发表博文,发现写博文比写一个demo还要时间长,怕写出来的东西有错误会误导你们(有错误请你们评论指出~),因此会去查不少相关资料,这个过程也是学习的过程,之后会常常写写博文滴!最后,但愿你们经过这篇文章也能学会本身作这种可视化的效果,配合一些可视化库还能作出很酷炫的效果呢,一块儿互相学习进步吧,加油!(。・д・。)