【HTML5】Web Audio API打造超炫的音乐可视化效果
HTML5真是太多炫酷的东西了,其中Web Audio API算一个,琢磨着弄了个音乐可视化的demo,先上效果图:
项目演示:别说话,点我! 源码已经挂到github上了,有兴趣的同学也可以去star或者fork我,源码注释超清楚的哦~~之前看刘大神的文章和源码,感觉其他方面的内容太多了,对初学者来说可能一下子难以抓到Web Audio API的重点,所以我就从一个初学者的角度来给大家说说Web Audio API这些事吧。
Web Audio API与HTML5提供的Audio标签并不是同样的东西,他们之间的区别可以自行搜索。简单的说Audio就是一个自带GUI的标签,他对音频的操作还是比较弱的,而Web Audio API则封装了非常多的对音频的操作,功能十分强大。Web Audio API目前还是一个草案,最新版本可以浏览这里:https://www.w3.org/TR/webaudio/,当然还有MDN上面的介绍:https://developer.mozilla.org/en-US/docs/Web/API/Web_Audio_API#Example
注意:由于Web Audio API还是非常新,所以现在浏览器的支持不是非常好,具体兼容性如下:
一、整体流程:
一般来说,一个典型的Audio应用的流程都是这样的:
1、创建音频环境(audio Context)
2、创建音源,可以通过audio标签、文件流等方式
3、创建效果节点,可以是混响、压缩、延迟、增益等效果器、也可以是分析节点。
4、为音频选一个输出,比说说你的扬声器。然后将源、效果器和输出连接起来。
玩过吉他或者贝斯的哥们应该很容易理解了:音频环境就是你整个乐器硬件系统,音源就是你的乐器,效果节点就是各种效果器,当然也需要音箱输出和各种连线了。要对音乐进行处理,最重要的就是效果器节点了。这个项目没有增加其他效果,只用了一个分析节点,用来量化音频信号再关联到图形元素上面,得到可视化效果。
二、最简版本代码
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>简单测试</title>
</head>
<body>
<canvas id="myCanvas" width="400" height="400">您的浏览器不支持canvas标签</canvas>
<audio id="myAudio" src="simplelove.mp3" controls="controls" >您的浏览器不支持audio标签</audio>
<script type="text/javascript">
window.onload = function () {
try {
var audioCtx = new (window.AudioContext ||window.webkitAudioContext)();
} catch (err) {
alert('!Your browser does not support Web Audio API!');
};
var myCanvas = document.getElementById('myCanvas'),
canvasCtx = myCanvas.getContext("2d"),
myAudio = document.getElementById("myAudio"),
source = audioCtx.createMediaElementSource(myAudio),
analyser = audioCtx.createAnalyser();
source.connect(analyser);
analyser.connect(audioCtx.destination);
myAudio.oncanplaythrough = function draw () {
var cwidth = myCanvas.width,
cheight = myCanvas.height,
array = new Uint8Array(128);
analyser.getByteFrequencyData(array);
canvasCtx.clearRect(0, 0, cwidth, cheight);
for (var i = 0; i < array.length; i++) {
canvasCtx.fillRect(i * 3, cheight - array[i], 2, cheight);
}
requestAnimationFrame(draw);
};
};
</script>
</body>
</html>
这个是最精简的版本,相信可以帮助大家快速理解整个流程,但是需要注意的是这个版本不兼容移动端。
三、关键代码分析
1、创建音频环境(audio Context)
创建音频环境非常简单,但是要考虑到浏览器兼容问题,需要用一个或语句来兼容不同浏览器。另外,对于不支持AudioContext的浏览器,还需要加一个try catch来避免报错。
try {
var audioCtx = new (window.AudioContext ||window.webkitAudioContext)();
} catch (err) {
alert('!Your browser does not support Web Audio API!');
};
2、创建音源
创建音源的方法有以下几种:
1) 直接从HTML的video/audio元素中获取,使用的方法为AudioContext.createMediaElementSource()
.
var source = audioCtx.createMediaElementSource(myMediaElement); //myMediaElement可以是页面中的元素,也可以是用new创建的audio/video对象
注意:由于audio标签在移动端还有相当多的坑,大家可以搜索一下,避免踩坑哈!如果不考虑移动端的话,最简单的办法就是这种啦。
2) 从原始的PCM数据中创建音源,方法有AudioContext.createBuffer()
,AudioContext.createBufferSource()
, 和AudioContext.decodeAudioData()
. 这里可以分成从网络中获取或者从本地文件中选择两种方式,关键代码如下。
var source = audioCtx.createBufferSource(); //创建一个空的音源,一般使用该方式,后续将解码的缓冲数据放入source中,直接对source操作。
// var buffer = audioCtx.createBuffer(2, 22050, 44100); //创建一个双通道、22050帧,44.1k采样率的缓冲数据。
audioCtx.decodeAudioData(audioData, function(buffer) {
source.buffer = buffer; //将解码出来的数据放入source中
//其他操作
}, function(err){
alert('!Fail to decode the file!'); //解码出错处理
});
网络获取方式,需要开一个Ajax异步请求来获取文件,并且设置请求返回类型为'ArrayBuffer',代码如下:
// use XHR to load an audio track, and
// decodeAudioData to decode it and stick it in a buffer.
// Then we put the buffer into the source
function getData() {
var request = new XMLHttpRequest(); //开一个请求
request.open('GET', url, true); //往url请求数据
request.responseType = 'arraybuffer'; //设置返回数据类型
request.onload = function() {
var audioData = request.response;
//数据缓冲完成之后,进行解码
audioCtx.decodeAudioData(audioData, function(buffer) {
source.buffer = buffer; //将解码出来的数据放入source中
//进行数据处理
}, function(err) {
alert(‘!Fail to decode the file!’); //解码出错处理
});
};
request.send();
}
本地获取的话需要通过文件类型的input标签来进行文件选择,监听input的onchnage事件,一担文件选中便开始在代码中进行文件读入,此处用到file reader来读取文件,同样的读取结果的数据类型也设置为'ArrayBuffer'。我的项目使用了file reader本地读取的办法,兼顾移动端。
var audioInput = document.getElementById("uploader"); //HTML语句:<input type="file" id="uploader" />
audioInput.onchange = function() {
//文件长度不为0则真的选中了文件,因为用户点击取消也会触发onchange事件。
if (audioInput.files.length !== 0) {
files = audioInput.files[0]; //得到用户选取的文件
//文件选定之后,马上用FileReader进行读入
fr = new FileReader();
fr.onload = function(e) {
var fileResult = e.target.result;
//文件读入完成,进行解码
audioCtx.decodeAudioData(fileResult, function(buffer) {
source.buffer = buffer;//将解码出来的数据放入source中
//转到播放和分析环节 }, function(err) {
alert('!Fail to decode the file'); //解码出错
});
};
fr.onerror = function(err) {
alert('!Fail to read the file'); //文件读入出错
};
fr.readAsArrayBuffer(rfile); //同样的,ArrayBuffer方式读取
}
};
3、创建效果节点,选择输出并将源、效果器和输出连接起来。
前面也说过了,效果有非常多种,本文是建立分析节点。这里的连接是音源>>分析节点>>输出,为什么不直接将音源和输出连接起来呢?其实也可以直连,但因为分析节点需要做一定的处理,如果直接将音源和输出连接的话会有一定的延迟。另外,这里定义了一个status参数,用来指示状态值。最后的启动有两种写法,有兴趣的同学再去MDN上查查吧。
var status = 0, //状态,播放中:1,停止:0
arraySize = 128, //可以得到128组频率值
analyser = audioCtx.createAnalyser(); //创建分析节点
source.connect(analyser); //将音源和分析节点连接在一起
analyser.connect(audioCtx.destination); //将分析节点和输出连接在一起
source.start(0); //启动音源
status = 1; //更改音频状态
4、可视化绘图
为了得到可视化效果,还需要对分析节点做一个傅里叶变换将信号从时域转到频域中,此处原理省略一万字。。。有兴趣的同学可以去看看信号处理相关的书籍。但是,这么难得的装逼机会,我舍不得呀!看不惯的同学可以跳过,也可以微信转账给我(什么鬼。。。)。
项目中getByteFrequencyData(array)这个函数来获取所需频率的能量值,其中array数组的长度为频率的个数。有看过资料的同学会发现这里很多时候用的是analyser.frequencyBinCount和analyser.fftsize两个值,其中analyser.fftsize是快速傅立叶变换(FFT)用于频域分析的尺寸,默认为2048。analyser.frequencyBinCount是fftsize的一半,这里我不需要那么多组数据,所以自定义了一个长度为128的8位无符号整形数组(谭浩强C语言后遗症,勿怪)。另外需要注意的是频率值的范围,是0到255,如果需要精度更高的值,可以使用AnalyserNode.getFloatFrequencyData()得到32位浮点数。经过上面的得到了这些值之后,我们就可以用它来跟某些视觉元素关联起来,比如说常见的柱状频谱的高度、圆的半径、线条的密度等等。我的项目里面采用的是能量球的方式。
var canvas = document.getElementById('drawCanvas'),
ctx = canvas.getContext('2d'),
cwidth = canvas.width,
cheight = canvas.height,
visualizer = [], //可视化形状
animationId = null;
var random = function(m, n) {
return Math.round(Math.random() * (n - m) + m); //返回m~n之间的随机数
};
for (var i = 0; i < num; i++) {
var x = random(0, cwidth),
y = random(0, cheight),
color = "rgba(" + random(0, 255) + "," + random(0, 255) + "," + random(0, 255) + ",0)"; //随机化颜色
visualizer.push({
x: x,
y: y,
dy: Math.random() + 0.1, //保证dy>0.1
color: color,
radius: 30 //能量球初始化半径
});
} var draw = function() {
var array = new Uint8Array(128); //创建频率数组
analyser.getByteFrequencyData(array); //分析频率信息,结果返回到array数组中,频率值范围:0~255
if (status === 0) {
//array数组归零,有时候音频播完了但是频率值还残留着,这时候需要强制清零
for (var i = array.length - 1; i >= 0; i--) {
array[i] = 0;
};
var allBallstoZero = true; //能量球归零
for (var i = that.visualizer.length - 1; i >= 0; i--) {
allBallstoZero = allBallstoZero && (visualizer[i].radius < 1);
};
if (allBallstoZero) {
cancelAnimationFrame(animationId); //结束动画
return;
};
};
ctx.clearRect(0, 0, cwidth, cheight);
for (var n = 0; n < array.length; n++) {
var s = visualizer[n];
s.radius = Math.round(array[n] / 256 * (cwidth > cheight ? cwidth / 25 : cheight / 18)); //控制能量球大小与画布大小的比例
var gradient = ctx.createRadialGradient(s.x, s.y, 0, s.x, s.y, s.radius); //创建能量球的渐变样式
gradient.addColorStop(0, "#fff");
gradient.addColorStop(0.5, "#D2BEC0");
gradient.addColorStop(0.75, s.color.substring(0, s.color.lastIndexOf(",")) + ",0.4)");
gradient.addColorStop(1, s.color);
ctx.fillStyle = gradient;
ctx.beginPath();
ctx.arc(s.x, s.y, s.radius, 0, Math.PI * 2, true); //画一个能量球
ctx.fill();
s.y = s.y - 2 * s.dy; //能量球向上移动
if ((s.y <= 0) && (status != 0)) { //到画布顶端的时候重置s.y,随机化s.x
s.y = cheight;
s.x = random(0, cwidth);
}
}
animationId = requestAnimationFrame(draw); //动画
};
5、其他
音频播完:
source.onended = function() {
status = 0; //更改播放状态
};
自适应方面,在window的onload和onresize调整canvas尺寸,但重点不在这里。重点是能量球的大小如何跟随画布大小调节,因为动画在进行中,所以最好的方案也是在动画中动态改变,所以上段代码中的cwidth和cheight的赋值应该放在绘图动画中。
var canvas = document.getElementById('drawCanvas');
canvas.width = window.clientWidth
|| document.documentElement.clientWidth
|| document.body.clientWidth;
canvas.height = window.clientHeight
|| document.documentElement.clientHeight
|| document.body.clientHeight;
增加点小玩法:鼠标捕捉能量球,鼠标进入能量球内的时候就捕捉到能量球,并要保持移动才能持续抓紧能量球
//鼠标捕捉能量球
canvas.onmousemove = function (e) {
if (status != 0) {
for (var n = 0; n < visualizer.length; n++) {
var s = visualizer[n];
if (Math.sqrt(Math.pow(s.x-e.pageX,2) + Math.pow(s.y-e.pageY,2)) < s.radius) {
s.x = e.pageX;
s.y = e.pageY;
}
}
}
};
最后说说非常重要的代码规范!
上述代码只是为了简化关系突出流程而改写的代码,并不符合代码规范。为了更好地进行编码,我们应该创建一个全局对象,把上述所有相关属性及方法写到其中。全局对象不但可以方便管理,而且在chrome中调试的时候,可以直接在控制台中查看并编辑,调试起来非常方便。按照惯例,对象的属性直接写在构造器里,对象的方法写到原型中方便日后扩展继承。对象内部使用的私有方法还有私有属性以短横线开头,也是从封装的角度考虑的。
这篇文章主要是参考了刘哇勇的博文和代码(下2),后来查看MDN的时候发现了下1的更通俗简单的版本,都是大力推荐!写文章真不容易,首先是思路重新整理,然后是材料的收集,代码的增减,工作量真心大。写作不易,赶紧点赞哈~
Reference:
1、Visualizations with Web Audio API(官方原版,强力推荐) https://developer.mozilla.org/en-US/docs/Web/API/Web_Audio_API/Visualizations_with_Web_Audio_API
2、开大你的音响,感受HTML5 Audio API带来的视听盛宴 http://www.cnblogs.com/Wayou/p/html5_audio_api_visualizer.html
【HTML5】Web Audio API打造超炫的音乐可视化效果的更多相关文章
- HTML5 ——web audio API 音乐可视化(二)
上一篇 web audio API 音乐可视化(一)介绍了一些基本的API,以及如何简单的播放一个音频,本篇介绍一下怎么对获取到的音频进行分析,并将分析后的数据绘制成图像. 最终效果请戳这里; 完整版 ...
- HTML5 ——web audio API 音乐可视化(一)
使用Web Audio API可以对音频进行分析和操作,最终实现一个音频可视化程序. 最终效果请戳这里; 完整版代码请戳这里,如果还看得过眼,请给一个start⭐ 一.API AudioContext ...
- 关于HTML5音频——audio标签和Web Audio API各平台浏览器的支持情况
对比audio标签 和 Web Audio API 各平台浏览器的支持情况: audio element Web Audio API desktop browsers Chrome 14 Yes ...
- 【Web Audio API】 — 那些年的 web audio
转 TAT.Jdo:[Web Audio API] - 那些年的 web audio 这主题主要是早期对 web audio api的一些尝试,这里整理一下以便以后翻阅,如有错误,诚请指正. 在这之前 ...
- Web Audio API 实现音频可视化
声明:本文为原创文章,如需转载,请注明来源WAxes,谢谢! 一转眼就已经有三个月没写博客了,毕业季事情确实多,现在也终于完全毕业了,博客还是不能落下.偶尔还是要写一下. 玩HTML5的Audio A ...
- [Javascript] Intro to the Web Audio API
An introduction to the Web Audio API. In this lesson, we cover creating an audio context and an osci ...
- 关于Web Audio API的入门
Web Audio API提供了一个简单强大的机制来实现控制web应用程序的音频内容.它允许你开发复杂的混音,音效,平移以及更多. 可以先看一下MDN的这篇文章<Web Audio API的运用 ...
- 使用Web Audio API绘制音波图
摘要:Web Audio API是对<audio> 标签功能上的补充,我们可以用它完成混音.音效.平移等各种复杂的音频处理,本文简单的使用其完成音波图的绘制. PS:本例子使用ES6编程, ...
- H5的Web Audio Api
概述 研究Web Audio Api的主要原因是:工作中需要在ios中实现声音的淡出效果,主要是通过setInterval来改audio标签的volume属性实现的,但是ios上面volume属性是只 ...
随机推荐
- vim简明教程
在shell中新建一个文件 # vim a.txt vim有三种模式:一般模式.插入模式.底行模式 三种工作模式 1.命令模式 移动光标 hjkl yy 复制 nyy 从光标向下复制n行 0 移动光标 ...
- Flume(3)source组件之NetcatSource使用介绍
一.概述: 本节首先提供一个基于netcat的source+channel(memory)+sink(logger)的数据传输过程.然后剖析一下NetcatSource中的代码执行逻辑. 二.flum ...
- Android应用请求获取Root权限
应用获取Root权限的原理:让应用的代码执行目录获取最高权限.在Linux中通过chmod 777 [代码执行目录] /** * 应用程序运行命令获取 Root权限,设备必须已破解(获得ROOT权限) ...
- CSS 代码技巧与维护 ★ Mozilla Hacks – the Web developer blog
原文链接:https://hacks.mozilla.org/2016/05/css-coding-techniques/ 译文链接 :http://www.zcfy.cc/article/css-c ...
- linux网络编程
A: osi七层: 应用层 用 表示层 户 会话层 态 ************ ...
- gradle各版本下载地址
gradle各版本下载地址:http://services.gradle.org/distributions 以前都是手动下载gradle的文件,然后修改的,今天想从一些博客网站上下载最新的gradl ...
- 域名解析与多域名绑定多个Tomcat项目
第一步.域名解析 1.登录阿里云的服务器地址:https://www.aliyun.com/ 新手礼包地址:https://s.click.taobao.com/as9o9Ox 2.点击控制台 3 ...
- 禁用ViewPager边界滑动效果(转)
反射设置方法 private EdgeEffectCompat leftEdge; private EdgeEffectCompat rightEdge; public void DisableLRS ...
- brew管理node的版本
摘要 nvm可以.brew怎么去切换不同的node版本 转载请注明出处:http://my.oschina.net/uniquejava/blog/491030 brew详解:http://stack ...
- 安卓手机USB网络共享,电脑卡顿、反应慢
1.首先需要把手机连接到电脑,在手机上打开USB网络共享. 2.打开设备管理器 3.在网络适配器中,找到Remote NDIS based Internet Sharing Device,右键更新驱动 ...