kefu/static/js/audio.js

351 lines
13 KiB
JavaScript
Raw Normal View History

2024-12-10 02:50:12 +00:00
/**
* Web音频数据可视化模块
* @author Margox
* @version 0.0.1
*/
(function(factory){
/*
* 添加UMD支持
*/
if (typeof exports === 'object') {
module.exports = factory();
} else if (typeof define === 'function' && define.amd) {
define(factory);
} else {
window.Vudio = factory();
}
})(function() {
'use strict';
// 默认参数
var __default_option = {
effect : 'waveform',
accuracy : 128,
width : 256,
height : 100,
waveform : {
maxHeight : 80,
minHeight : 1,
spacing : 1,
color : '#f00',
shadowBlur : 0,
shadowColor : '#f00',
fadeSide : true,
horizontalAlign : 'center',
verticalAlign : 'middle',
prettify : true
},
lighting : {
maxHeight : 80,
lineWidth: 0,
color : '#f00',
shadowBlur : 0,
shadowColor : '#f00',
fadeSide : true,
horizontalAlign : 'center',
verticalAlign : 'middle'
}
}
/**
* 构造函数
* @param {object} audioSource HTMLAudioSource/MediaStream
* @param {object} canvasElement HTMLCanvasElement
* @param {object} option 可选配置参数
*/
function Vudio(audioSource, canvasElement, option) {
if (['[object HTMLAudioSource]', '[object HTMLAudioElement]', '[object MediaStream]'].indexOf(Object.prototype.toString.call(audioSource)) === -1) {
throw new TypeError('Invaild Audio Source');
}
if (Object.prototype.toString.call(canvasElement) !== '[object HTMLCanvasElement]') {
throw new TypeError('Invaild Canvas Element');
}
this.audioSrc = audioSource;
this.canvasEle = canvasElement;
this.option = __mergeOption(__default_option, option);
this.meta = {};
this.stat = 0;
this.freqByteData = null;
this.__init();
}
// private functions
function __mergeOption() {
var __result = {}
Array.prototype.forEach.call(arguments, function(argument) {
var __prop;
var __value;
for (__prop in argument) {
if (Object.prototype.hasOwnProperty.call(argument, __prop)) {
if (Object.prototype.toString.call(argument[__prop]) === '[object Object]') {
__result[__prop] = __mergeOption(__result[__prop], argument[__prop]);
} else {
__result[__prop] = argument[__prop];
}
}
}
});
return __result;
}
Vudio.prototype = {
__init : function() {
var audioContext = new (window.AudioContext || window.webkitAudioContext || window.mozAudioContext),
source = Object.prototype.toString.call(this.audioSrc) !== '[object MediaStream]' ? audioContext.createMediaElementSource(this.audioSrc) : audioContext.createMediaStreamSource(this.audioSrc),
dpr = window.devicePixelRatio || 1;
this.analyser = audioContext.createAnalyser();
this.meta.spr = audioContext.sampleRate;
source.connect(this.analyser);
this.analyser.fftSize = this.option.accuracy * 2;
this.analyser.connect(audioContext.destination);
this.freqByteData = new Uint8Array(this.analyser.frequencyBinCount);
this.context2d = this.canvasEle.getContext('2d');
this.width = this.option.width;
this.height = this.option.height;
// ready for HD screen
this.context2d.canvas.width = this.width * dpr;
this.context2d.canvas.height = this.height * dpr;
this.context2d.scale(dpr, dpr);
},
__rebuildData : function (freqByteData, horizontalAlign) {
var __freqByteData;
if (horizontalAlign === 'center') {
__freqByteData = [].concat(
Array.from(freqByteData).reverse().splice(this.option.accuracy / 2, this.option.accuracy / 2),
Array.from(freqByteData).splice(0, this.option.accuracy / 2)
);
} else if (horizontalAlign === 'left') {
__freqByteData = freqByteData;
} else if (horizontalAlign === 'right') {
__freqByteData = Array.from(freqByteData).reverse();
} else {
__freqByteData = [].concat(
Array.from(freqByteData).reverse().splice(this.option.accuracy / 2, this.option.accuracy / 2),
Array.from(freqByteData).splice(0, this.option.accuracy / 2)
);
}
return __freqByteData;
},
__animate : function() {
if (this.stat === 1) {
this.analyser.getByteFrequencyData(this.freqByteData);
(typeof this.__effects()[this.option.effect] === 'function') && this.__effects()[this.option.effect](this.freqByteData);
requestAnimationFrame(this.__animate.bind(this));
}
},
__testFrame : function() {
this.analyser.getByteFrequencyData(this.freqByteData);
(typeof this.__effects()[this.option.effect] === 'function') && this.__effects()[this.option.effect](this.freqByteData);
},
// effect functions
__effects : function() {
var __that = this;
return {
lighting : function(freqByteData) {
var __lightingOption = __that.option.lighting;
var __freqByteData = __that.__rebuildData(freqByteData, __lightingOption.horizontalAlign);
var __maxHeight = __lightingOption.maxHeight / 2;
var __isStart = true, __fadeSide = true, __x, __y;
if (__lightingOption.horizontalAlign !== 'center') {
__fadeSide = false;
}
// clear canvas
__that.context2d.clearRect(0, 0, __that.width, __that.height);
// draw lighting
__that.context2d.lineWidth = __lightingOption.lineWidth;
__that.context2d.strokeStyle = __lightingOption.color;
__that.context2d.beginPath();
__freqByteData.forEach(function(value, index) {
__x = __that.width / __that.option.accuracy * index;
__y = value / 256 * __maxHeight;
if (__lightingOption.verticalAlign === 'middle') {
__y = (__that.height - value) / 2 - __maxHeight / 2;
} else if (__lightingOption.verticalAlign === 'bottom') {
__y = __that.height - value;
} else if (__lightingOption.verticalAlign === 'top') {
__y = value;
} else {
__y = (__that.height - value) / 2 - __maxHeight / 2;
}
if (__isStart) {
__that.context2d.moveTo(__x, __y);
__isStart = false;
} else {
__that.context2d.lineTo(__x, __y);
}
});
__that.context2d.stroke();
},
waveform : function (freqByteData) {
var __waveformOption = __that.option.waveform;
var __fadeSide = __waveformOption.fadeSide;
var __prettify = __waveformOption.prettify;
var __freqByteData = __that.__rebuildData(freqByteData, __waveformOption.horizontalAlign);
var __maxHeight, __width, __height, __left, __top, __color, __linearGradient, __pos;
if (__waveformOption.horizontalAlign !== 'center') {
__fadeSide = false;
__prettify = false;
}
// clear canvas
__that.context2d.clearRect(0, 0, __that.width, __that.height);
// draw waveform
__freqByteData.forEach(function(value, index){
__width = (__that.width - __that.option.accuracy * __waveformOption.spacing) / __that.option.accuracy;
__left = index * (__width + __waveformOption.spacing);
__waveformOption.spacing !== 1 && (__left += __waveformOption.spacing / 2);
if (__prettify) {
if (index <= __that.option.accuracy / 2) {
__maxHeight = (1 - (__that.option.accuracy / 2 - 1 - index) / ( __that.option.accuracy / 2)) * __waveformOption.maxHeight;
} else {
__maxHeight = (1 - (index - __that.option.accuracy / 2) / ( __that.option.accuracy / 2)) * __waveformOption.maxHeight;
}
} else {
__maxHeight = __waveformOption.maxHeight;
}
__height = value / 256 * __maxHeight;
__height = __height < __waveformOption.minHeight ? __waveformOption.minHeight : __height;
if (__waveformOption.verticalAlign === 'middle') {
__top = (__that.height - __height) / 2;
} else if (__waveformOption.verticalAlign === 'top') {
__top = 0;
} else if (__waveformOption.verticalAlign === 'bottom') {
__top = __that.height - __height;
} else {
__top = (__that.height - __height) / 2;
}
__color = __waveformOption.color;
if (__color instanceof Array) {
__linearGradient = __that.context2d.createLinearGradient(
__left,
__top,
__left,
__top + __height
);
__color.forEach(function(color, index) {
if (color instanceof Array) {
__pos = color[0];
color = color[1];
} else if (index === 0 || index === __color.length - 1) {
__pos = index / (__color.length - 1);
} else {
__pos = index / __color.length + 0.5 / __color.length;
}
__linearGradient.addColorStop(__pos, color);
});
__that.context2d.fillStyle = __linearGradient;
} else {
__that.context2d.fillStyle = __color;
}
if (__waveformOption.shadowBlur > 0) {
__that.context2d.shadowBlur = __waveformOption.shadowBlur;
__that.context2d.shadowColor = __waveformOption.shadowColor;
}
if (__fadeSide) {
if (index <= __that.option.accuracy / 2) {
__that.context2d.globalAlpha = 1 - (__that.option.accuracy / 2 - 1 - index) / ( __that.option.accuracy / 2);
} else {
__that.context2d.globalAlpha = 1 - (index - __that.option.accuracy / 2) / ( __that.option.accuracy / 2);
}
} else {
__that.context2d.globalAlpha = 1;
}
__that.context2d.fillRect(__left, __top, __width, __height);
});
}
}
},
// 开始
dance : function() {
if (this.stat === 0) {
this.stat = 1;
this.__animate();
}
return this;
},
// 暂停
pause : function() {
this.stat = 0;
return this;
},
// 改变参数
setOption : function(option) {
this.option = __mergeOption(this.option, option);
}
};
return Vudio;
});