123 lines
5.7 KiB
JavaScript
123 lines
5.7 KiB
JavaScript
|
|
var Visualizer = function() {
|
|
this.file = null, //the current file
|
|
this.fileName = null, //the current file name
|
|
this.audioContext = null,
|
|
this.source = null, //the audio source
|
|
this.info = document.getElementById('info').innerHTML, //this used to upgrade the UI information
|
|
this.infoUpdateId = null, //to sotore the setTimeout ID and clear the interval
|
|
this.animationId = null,
|
|
this.status = 0, //flag for sound is playing 1 or stopped 0
|
|
this.forceStop = false,
|
|
this.allCapsReachBottom = false
|
|
};
|
|
|
|
|
|
Visualizer.prototype = {
|
|
ini: function() {
|
|
this._prepareAPI();
|
|
},
|
|
_prepareAPI: function() {
|
|
//fix browser vender for AudioContext and requestAnimationFrame
|
|
window.AudioContext = window.AudioContext || window.webkitAudioContext || window.mozAudioContext || window.msAudioContext;
|
|
window.requestAnimationFrame = window.requestAnimationFrame || window.webkitRequestAnimationFrame || window.mozRequestAnimationFrame || window.msRequestAnimationFrame;
|
|
window.cancelAnimationFrame = window.cancelAnimationFrame || window.webkitCancelAnimationFrame || window.mozCancelAnimationFrame || window.msCancelAnimationFrame;
|
|
try {
|
|
this.audioContext = new AudioContext();
|
|
} catch (e) {
|
|
alert('!Your browser does not support AudioContext', false);
|
|
console.log(e);
|
|
}
|
|
},
|
|
_visualize: function(url, canvas, audio) {
|
|
var audioContext = this.audioContext,
|
|
audioBufferSouceNode = audioContext.createMediaElementSource(audio),
|
|
analyser = audioContext.createAnalyser(),
|
|
that = this;
|
|
this.analyser = analyser;
|
|
this.canvas = canvas;
|
|
//connect the source to the analyser
|
|
audioBufferSouceNode.connect(analyser);
|
|
//connect the analyser to the destination(the speaker), or we won't hear the sound
|
|
analyser.connect(audioContext.destination);
|
|
//then assign the buffer to the buffer source node
|
|
//play the source
|
|
if (!audioBufferSouceNode.start) {
|
|
audioBufferSouceNode.start = audioBufferSouceNode.noteOn //in old browsers use noteOn method
|
|
audioBufferSouceNode.stop = audioBufferSouceNode.noteOff //in old browsers use noteOn method
|
|
};
|
|
//stop the previous sound if any
|
|
if (this.animationId !== null) {
|
|
cancelAnimationFrame(this.animationId);
|
|
}
|
|
if (this.source !== null) {
|
|
this.source.stop(0);
|
|
}
|
|
//audioBufferSouceNode.start(0);
|
|
this.status = 1;
|
|
this.source = audioBufferSouceNode;
|
|
this._drawSpectrum(analyser, canvas);
|
|
},
|
|
_restart: function() {
|
|
this._drawSpectrum(this.analyser, this.canvas);
|
|
},
|
|
_stop: function() {
|
|
cancelAnimationFrame(this.animationId);
|
|
},
|
|
_drawSpectrum: function(analyser, canvas) {
|
|
var that = this,
|
|
monoL = canvas,
|
|
//monoR = document.getElementById('mono-R'),
|
|
cwidth = monoL.width,
|
|
cheight = monoL.height - 3,
|
|
meterWidth = 1, //width of the meters in the spectrum
|
|
gap = 0.01, //gap between meters
|
|
capHeight = 2,
|
|
capStyle = '#fff',
|
|
meterNum = 20 * (2 + 2), //count of the meters
|
|
capYPositionArray = []; ////store the vertical position of hte caps for the preivous frame
|
|
ctx = monoL.getContext('2d'),
|
|
gradient = ctx.createLinearGradient(0, 0, 0, 300);
|
|
gradient.addColorStop(1, themeColors.highlight);
|
|
gradient.addColorStop(0, '#fff');
|
|
|
|
var drawMeter = function() {
|
|
var array = new Uint8Array(analyser.frequencyBinCount);
|
|
analyser.getByteFrequencyData(array);
|
|
if (that.status === 0) {
|
|
//fix when some sounds end the value still not back to zero
|
|
for (var i = array.length - 1; i >= 0; i--) {
|
|
array[i] = 0;
|
|
};
|
|
allCapsReachBottom = true;
|
|
for (var i = capYPositionArray.length - 1; i >= 0; i--) {
|
|
allCapsReachBottom = allCapsReachBottom && (capYPositionArray[i] === 0);
|
|
};
|
|
if (allCapsReachBottom) {
|
|
cancelAnimationFrame(that.animationId); //since the sound is top and animation finished, stop the requestAnimation to prevent potential memory leak,THIS IS VERY IMPORTANT!
|
|
return;
|
|
};
|
|
};
|
|
var step = Math.round(array.length / meterNum); //sample limited data from the total array
|
|
ctx.clearRect(0, 0, cwidth, cheight);
|
|
for (var i = 0; i < meterNum; i++) {
|
|
var value = array[i * step];
|
|
if (capYPositionArray.length < Math.round(meterNum)) {
|
|
capYPositionArray.push(value);
|
|
};
|
|
ctx.fillStyle = capStyle;
|
|
//draw the cap, with transition effect
|
|
if (value < capYPositionArray[i]) {
|
|
ctx.fillRect(i * 12, cheight - (--capYPositionArray[i]), meterWidth, capHeight);
|
|
} else {
|
|
ctx.fillRect(i * 12, cheight - value, meterWidth, capHeight);
|
|
capYPositionArray[i] = value;
|
|
};
|
|
ctx.fillStyle = gradient; //set the filllStyle to gradient for a better look
|
|
ctx.fillRect(i * 12 /*meterWidth+gap*/ , cheight - value + capHeight, meterWidth, cheight); //the meter
|
|
}
|
|
that.animationId = requestAnimationFrame(drawMeter);
|
|
}
|
|
this.animationId = requestAnimationFrame(drawMeter);
|
|
}
|
|
} |