这段时间遇到个项目需要涉及音频流
的可视化开发,可H5 Web Audio API 都是好几年前在慕课网有接触过了,然后照着教程做了个小项目,然后一直都没再捡起来再看过,所以把以前的一个项目捡起来用ES6简单重构下,实现柱状和点状的可视化效果。
1. 柱状可视化音频效果
前端使用ES6
面向对象和Fetch
请求实现音频的可视化效果和资源获取,后端采用koa应用生成器生成koa2
项目、手动配置路由和ejs
模板渲染页面,以及文件模块fs
,获取音频文件数据,通过配置的路由,将数据返回到前端页面。
npm install koa-generator -g
koa1.2生成一个test项目,切到test目录并下载依赖
koa test -> cd test -> npm install
koa2生成一个test项目,切到test目录并下载依赖
koa2 test -> cd test -> npm install
运行 :
npm run start
访问 http://localhost:3000 就可以看到项目效果
/musc
(1) 在app.j中新增如下:
const music = require('./routes/music')
app.use(music.routes(), music.allowedMethods())
(2)在routes目录下新增文件路由文件 routes/music.js
,编辑如下:
const router = require('koa-router')()
const fs = require('fs');
const path = require('path');
const bodyparser = require('koa-bodyparser')
router.use(bodyparser())
router.prefix('/music')
const musicPath = path.resolve(path.dirname(__dirname),'./public/music/');
router.get('/', async function (ctx, next) {
let files = await readFiles();
//console.log(files);
await ctx.render('index', {
title: 'H5 Music Audio',
music: files
});
})
router.get('/file',async function(ctx, next) {
// 根据name值 获取相关的音乐文件信息
let filename = ctx.query.name;
let filepath = musicPath + '\\' + filename;
// 读取音乐文件信息
let fileData = await new Promise(function(resolve,reject){
fs.readFile(filepath,function(err, data){
resolve(data);
});
});
// console.log(fileData);
ctx.body = fileData
});
router.get('/bar', function (ctx, next) {
ctx.body = 'this is a music/bar response'
})
async function readFiles(){
return fs.readdirSync(musicPath);
}
module.exports = router
views/index.ejs
, 编辑如下
<html>
<head>
<title><%= title %>title>
<link rel='stylesheet' href='/stylesheets/style.css' />
head>
<body>
<header>
<h1><%= title %>h1>
<ul class="type" id="type">
<li data-type="dot">Dotli>
<li data-type="column" class="selected">Columnli>
ul>
<p>
Volume <input id="volume" type="range" min="0" max="100" value="30">
p>
header>
<div class="left">
<ul id="list">
<% music.forEach(function(name,index){ %>
<li title="<%= name %>" class="<%= index==0 ? 'selected': '' %>" data-index="<%= index %>"><%= name %>li>
<% }) %>
ul>
div>
<div class="right" id="box">div>
<script type="text/javascript" src="/javascripts/visualizer.js">script>
<script type="text/javascript" src="/javascripts/music.js">script>
body>
html>
public/javascript/music.js
(1) 初始化音频操作类 AuidoVisualization
class AuidoVisualization{
constructor(){
this.ac = null;
this.gainNode = null;
this.anayser = null;
this.bufferSource = null;
this.init()
}
//初始化音频节点资源
init(){
this.ac = new AudioContext();
this.gainNode = this.ac.createGain();
this.anayser = this.ac.createAnalyser();
this.bufferSource = this.ac.createBufferSource();
this.anayser.fftSize = 128*2;
this.bufferSource.connect(this.anayser);
this.anayser.connect(this.gainNode);
this.gainNode.connect(this.ac.destination);
}
// 获取音频资源
request({url,method,callback}){
fetch(url,{
method: method,
responseType: 'arraybuffer'
}).then(res => {
return res.arrayBuffer();
}).then(data => {
this.ac.decodeAudioData(data,(buffer) => {
// 回调函数
callback(buffer);
},(err) => {
console.log(err);
});
})
}
// 可视化音频数据
visualize(callback){
let arr = new Uint8Array(this.anayser.frequencyBinCount);
let self = this;
// 将分析到的音频数据存入 arr数组中
function musicVisible(){
self.anayser.getByteFrequencyData(arr);
callback(arr);
requestAnimationFrame(musicVisible);
}
musicVisible();
}
// 音频播放
play(){
this.bufferSource.start(0);
}
// 停止播放
stop(){
this.bufferSource.stop(0);
}
// 改变音量
changeVolume(v){
this.gainNode.gain.value = v;
}
}
(2) 初始化点状图操作类 Dot
// 点状
class Dot{
constructor({x,y,r,canvas,ctx,colors}){
this.x = x;
this.y = y;
this.r = r;
this.dy = Math.random()*1.2
this.canvas = canvas;
this.ctx = ctx;
this.colors = colors;
}
draw(){
this.ctx.beginPath();
this.ctx.arc(this.x,this.y,this.r,0,Math.PI*2,true);
this.fillRadial();
//this.ctx.fillStyle = '#fff';
//this.ctx.fill();
}
fillRadial(){
let radial = this.ctx.createRadialGradient(this.x,this.y,0,this.x,this.y,this.r);
radial.addColorStop(0, this.colors[0]);
radial.addColorStop(0.5, this.colors[1]);
radial.addColorStop(1, this.colors[2]);
this.ctx.fillStyle = radial;
this.ctx.fill();
}
update(maxRadius){
this.y -= this.dy;
if(this.y < - maxRadius){
this.y = this.canvas.height;
}
}
}
(3) 初始化柱状图操作类 Column
// 柱状
class Column{
constructor({w,h,x,y,canvas,ctx,colors}){
this.w = w;
this.h = h;
this.x = x;
this.y = y;
this.canvas = canvas;
this.ctx = ctx;
this.colors = colors;
}
draw(){
this.fillRects();
this.ctx.beginPath();
this.ctx.fillRect(this.x,this.y,this.w,this.h);
}
fillRects(){
let line = this.ctx.createLinearGradient(0,0,0,this.canvas.height);
line.addColorStop(0,this.colors[0]);
line.addColorStop(0.5,this.colors[1]);
line.addColorStop(1,this.colors[2]);
this.ctx.fillStyle = line;
}
update(){
}
}
(4) 初始化canvas操作类 Canvas
class Canvas{
constructor(){
this.box = document.querySelector('#box');
this.canvas = document.createElement('canvas');
this.ctx = this.canvas.getContext('2d');
this.music = new Music();
this.av = av;
this.canvasWidth = 0;
this.canvasHeight = 0;
this.size = 80;
// 图形绘制
this.type = 'column';
this.dots = [];
this.dotsPos = [];
this.columns = [];
this.caps = [];
this.wh = {
cw: 0,
ch: 0
};
this.init();
}
init(){
this.initCanvas();
}
// 初始化canvas盒子
initCanvas(){
this.box.appendChild(this.canvas);
let t = setTimeout(() => {
this.wh = {
cw: this.canvas.width,
ch: this.canvas.height
};
clearTimeout(t);
},500)
this.resizeCanvas();
window.addEventListener('resize',() => {
this.resizeCanvas();
this.initialChart();
//console.log(this.canvas.width,this.canvas.height,this.wh);
})
// 初始化图形
this.initialChart();
// 图形化音频数据
this.av.visualize((arr) => {
//console.log(arr);
this.drawRect(arr);
});
// 点击切换柱状图和点状图
let btns = document.querySelector('#type').children;
let self = this;
Array.prototype.slice.call(btns).forEach(v => {
v.onclick = function(){
for(var i=0;i<btns.length;i++){
btns[i].className = '';
}
this.className = 'selected';
self.type = this.dataset.type;
}
})
}
initialChart(){
this.columns = [];
this.caps = [];
// 初始化柱状
for(let i=0;i<this.size;i++){
//let color = [this.getRamdomColor(),this.getRamdomColor(),this.getRamdomColor()];
let color = ['red','yellow','green'];
this.columns.push(new Column({
x: 0,
y: 0,
w: 0,
h: 0,
ctx: this.ctx,
canvas: this.canvas,
colors:color
}));
// 初始化柱状帽
this.caps.push(new Cap({
x: 0,
y: 0,
w: 0,
h: 0,
ctx: this.ctx,
canvas: this.canvas,
colors:color
}));
}
//初始化点状图
this.initDotPos();
}
initDotPos(){
this.dotsPos = [];
// 初始化dot图形位置
for(let i=0;i<this.size;i++){
this.dotsPos.push(new Dot({
x: this.canvasWidth*Math.random(),
y: this.canvasHeight*Math.random(),
colors: ['rgba(255,255,255,0.8)',this.getRamdomColor(1),'rgba(255,255,255,0)'],
ctx: this.ctx,
canvas: this.canvas
}))
}
}
resizeCanvas(){
this.canvasWidth = this.box.clientWidth;
this.canvasHeight = this.box.clientHeight;
this.canvas.width = this.canvasWidth;
this.canvas.height = this.canvasHeight;
}
// 绘制柱状图
drawRect(arr){
this.ctx.clearRect(0,0,this.canvasWidth,this.canvasHeight);
let w = this.canvasWidth / this.size;
let columnWidth = w*0.6;
let radius = 60;
for(let i=0;i<this.size;i++){
if(this.type == 'column'){
/*this.columns = [];
this.caps = [];*/
let h = this.canvasHeight * (arr[i]/256);
let ch = columnWidth*0.5;
// 柱状图 属性变化
this.columns[i].x = w*i;
this.columns[i].y = this.canvasHeight-h;
this.columns[i].w = columnWidth;
this.columns[i].h = h;
this.columns[i].draw();
// 柱状帽
this.caps[i].x = w*i;
this.caps[i].y = this.canvasHeight - ( ch + this.caps[i].cap );
this.caps[i].w = columnWidth;
this.caps[i].h = ch;
this.caps[i].update(h);
this.caps[i].draw();
}else{
if((arr[i]/256)*radius == 0) return;
//计算比例
let scale = (this.canvasHeight >= this.canvasWidth ? this.wh.cw/this.canvasWidth : this.wh.ch/this.canvasHeight )
if(scale > 1) scale = 1;
// 点状
this.dotsPos[i].r = (arr[i]/256)*radius*scale;
this.dotsPos[i].draw();
this.dotsPos[i].update(radius);
}
}
}
// 绘制点状图
drawDot(){
this.dots.forEach(v => {
v.draw();
})
}
// 获取随机颜色
getRamdomColor(alphas){
let alpha = alphas || 1;
return `rgba(${255*Math.random()},${255*Math.random()},${255*Math.random()},${alpha})`;
}
// 获取两个数之间的随机数
getRamdomNumber(n,m){
return n + Math.floor((m-n)*Math.random());
}
}
public/javascript/music-2.js
(1)第一阶段柱状音频预览
(2)第一阶段点状音频预览
https://github.com/RiversCoder/canvas/tree/master/9_canvas_music_visualization
另附上Web audio API 原理图如下