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); } }