├── sources
└── screencapture.png
├── README.md
├── style
└── style.css
├── License.txt
├── index.html
├── js
└── html5_audio_visualizer.js
└── audio_visualizer_single_page_version.html
/sources/screencapture.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/wayou/HTML5_Audio_Visualizer/HEAD/sources/screencapture.png
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | HTML5 Audio Visualizer
2 | ======================
3 |
4 | An audio spectrum visualizer built with HTML5 Audio API
5 |
6 | Demo
7 | ---
8 | [See it in action](http://wayou.github.io/HTML5_Audio_Visualizer/).
9 |
10 | Screen Capture
11 | ---
12 |
13 | 
14 |
--------------------------------------------------------------------------------
/style/style.css:
--------------------------------------------------------------------------------
1 | html, body {
2 | margin: 0;
3 | font-family: arial, "Microsoft YaHei";
4 | background-color: #272822;
5 | color: #FEFEFE;
6 | }
7 | #fileWrapper{
8 | transition:all 0.5s ease;
9 | }
10 | #fileWrapper:hover{
11 | opacity: 1!important;
12 | }
13 |
14 | #visualizer_wrapper{
15 | text-align: center;
16 | }
17 | footer{
18 | position: fixed;
19 | bottom: 2px;
20 | color:#aaa;
21 | }
--------------------------------------------------------------------------------
/License.txt:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2016 Wayou Liu
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 | HTML5 Audio API showcase | Audio visualizer
7 |
8 |
9 |
10 |
11 |
12 |
13 | HTML5 Audio API showcase | An Audio Viusalizer
14 |
15 |
Drag&drop or select a file to play:
16 |
17 |
18 |
19 |
20 |
21 |
22 |
25 |
26 |
27 |
28 |
--------------------------------------------------------------------------------
/js/html5_audio_visualizer.js:
--------------------------------------------------------------------------------
1 | /**
2 | * An audio spectrum visualizer built with HTML5 Audio API
3 | * Author:Wayou
4 | * License: MIT
5 | * Feb 15, 2014
6 | */
7 | window.onload = function() {
8 | new Visualizer().ini();
9 | };
10 | var Visualizer = function() {
11 | this.file = null; //the current file
12 | this.fileName = null; //the current file name
13 | this.audioContext = null;
14 | this.source = null; //the audio source
15 | this.info = document.getElementById('info').innerHTML; //used to upgrade the UI information
16 | this.infoUpdateId = null; //to store the setTimeout ID and clear the interval
17 | this.animationId = null;
18 | this.status = 0; //flag for sound is playing 1 or stopped 0
19 | this.forceStop = false;
20 | this.allCapsReachBottom = false;
21 | };
22 | Visualizer.prototype = {
23 | ini: function() {
24 | this._prepareAPI();
25 | this._addEventListner();
26 | },
27 | _prepareAPI: function() {
28 | //fix browser vender for AudioContext and requestAnimationFrame
29 | window.AudioContext = window.AudioContext || window.webkitAudioContext || window.mozAudioContext || window.msAudioContext;
30 | window.requestAnimationFrame = window.requestAnimationFrame || window.webkitRequestAnimationFrame || window.mozRequestAnimationFrame || window.msRequestAnimationFrame;
31 | window.cancelAnimationFrame = window.cancelAnimationFrame || window.webkitCancelAnimationFrame || window.mozCancelAnimationFrame || window.msCancelAnimationFrame;
32 | try {
33 | this.audioContext = new AudioContext();
34 | } catch (e) {
35 | this._updateInfo('!Your browser does not support AudioContext', false);
36 | console.log(e);
37 | }
38 | },
39 | _addEventListner: function() {
40 | var that = this,
41 | audioInput = document.getElementById('uploadedFile'),
42 | dropContainer = document.getElementsByTagName("canvas")[0];
43 | //listen the file upload
44 | audioInput.onchange = function() {
45 | if (that.audioContext===null) {return;};
46 |
47 | //the if statement fixes the file selction cancle, because the onchange will trigger even the file selection been canceled
48 | if (audioInput.files.length !== 0) {
49 | //only process the first file
50 | that.file = audioInput.files[0];
51 | that.fileName = that.file.name;
52 | if (that.status === 1) {
53 | //the sound is still playing but we upload another file, so set the forceStop flag to true
54 | that.forceStop = true;
55 | };
56 | document.getElementById('fileWrapper').style.opacity = 1;
57 | that._updateInfo('Uploading', true);
58 | //once the file is ready,start the visualizer
59 | that._start();
60 | };
61 | };
62 | //listen the drag & drop
63 | dropContainer.addEventListener("dragenter", function() {
64 | document.getElementById('fileWrapper').style.opacity = 1;
65 | that._updateInfo('Drop it on the page', true);
66 | }, false);
67 | dropContainer.addEventListener("dragover", function(e) {
68 | e.stopPropagation();
69 | e.preventDefault();
70 | //set the drop mode
71 | e.dataTransfer.dropEffect = 'copy';
72 | }, false);
73 | dropContainer.addEventListener("dragleave", function() {
74 | document.getElementById('fileWrapper').style.opacity = 0.2;
75 | that._updateInfo(that.info, false);
76 | }, false);
77 | dropContainer.addEventListener("drop", function(e) {
78 | e.stopPropagation();
79 | e.preventDefault();
80 | if (that.audioContext===null) {return;};
81 | document.getElementById('fileWrapper').style.opacity = 1;
82 | that._updateInfo('Uploading', true);
83 | //get the dropped file
84 | that.file = e.dataTransfer.files[0];
85 | if (that.status === 1) {
86 | document.getElementById('fileWrapper').style.opacity = 1;
87 | that.forceStop = true;
88 | };
89 | that.fileName = that.file.name;
90 | //once the file is ready,start the visualizer
91 | that._start();
92 | }, false);
93 | },
94 | _start: function() {
95 | //read and decode the file into audio array buffer
96 | var that = this,
97 | file = this.file,
98 | fr = new FileReader();
99 | fr.onload = function(e) {
100 | var fileResult = e.target.result;
101 | var audioContext = that.audioContext;
102 | if (audioContext === null) {
103 | return;
104 | };
105 | that._updateInfo('Decoding the audio', true);
106 | audioContext.decodeAudioData(fileResult, function(buffer) {
107 | that._updateInfo('Decode succussfully,start the visualizer', true);
108 | that._visualize(audioContext, buffer);
109 | }, function(e) {
110 | that._updateInfo('!Fail to decode the file', false);
111 | console.error(e);
112 | });
113 | };
114 | fr.onerror = function(e) {
115 | that._updateInfo('!Fail to read the file', false);
116 | console.error(e);
117 | };
118 | //assign the file to the reader
119 | this._updateInfo('Starting read the file', true);
120 | fr.readAsArrayBuffer(file);
121 | },
122 | _visualize: function(audioContext, buffer) {
123 | var audioBufferSouceNode = audioContext.createBufferSource(),
124 | analyser = audioContext.createAnalyser(),
125 | that = this;
126 | //connect the source to the analyser
127 | audioBufferSouceNode.connect(analyser);
128 | //connect the analyser to the destination(the speaker), or we won't hear the sound
129 | analyser.connect(audioContext.destination);
130 | //then assign the buffer to the buffer source node
131 | audioBufferSouceNode.buffer = buffer;
132 | //play the source
133 | if (!audioBufferSouceNode.start) {
134 | audioBufferSouceNode.start = audioBufferSouceNode.noteOn //in old browsers use noteOn method
135 | audioBufferSouceNode.stop = audioBufferSouceNode.noteOff //in old browsers use noteOff method
136 | };
137 | //stop the previous sound if any
138 | if (this.animationId !== null) {
139 | cancelAnimationFrame(this.animationId);
140 | }
141 | if (this.source !== null) {
142 | this.source.stop(0);
143 | }
144 | audioBufferSouceNode.start(0);
145 | this.status = 1;
146 | this.source = audioBufferSouceNode;
147 | audioBufferSouceNode.onended = function() {
148 | that._audioEnd(that);
149 | };
150 | this._updateInfo('Playing ' + this.fileName, false);
151 | this.info = 'Playing ' + this.fileName;
152 | document.getElementById('fileWrapper').style.opacity = 0.2;
153 | this._drawSpectrum(analyser);
154 | },
155 | _drawSpectrum: function(analyser) {
156 | var that = this,
157 | canvas = document.getElementById('canvas'),
158 | cwidth = canvas.width,
159 | cheight = canvas.height - 2,
160 | meterWidth = 10, //width of the meters in the spectrum
161 | gap = 2, //gap between meters
162 | capHeight = 2,
163 | capStyle = '#fff',
164 | meterNum = 800 / (10 + 2), //count of the meters
165 | capYPositionArray = []; ////store the vertical position of hte caps for the preivous frame
166 | ctx = canvas.getContext('2d'),
167 | gradient = ctx.createLinearGradient(0, 0, 0, 300);
168 | gradient.addColorStop(1, '#0f0');
169 | gradient.addColorStop(0.5, '#ff0');
170 | gradient.addColorStop(0, '#f00');
171 | var drawMeter = function() {
172 | var array = new Uint8Array(analyser.frequencyBinCount);
173 | analyser.getByteFrequencyData(array);
174 | if (that.status === 0) {
175 | //fix when some sounds end the value still not back to zero
176 | for (var i = array.length - 1; i >= 0; i--) {
177 | array[i] = 0;
178 | };
179 | allCapsReachBottom = true;
180 | for (var i = capYPositionArray.length - 1; i >= 0; i--) {
181 | allCapsReachBottom = allCapsReachBottom && (capYPositionArray[i] === 0);
182 | };
183 | if (allCapsReachBottom) {
184 | cancelAnimationFrame(that.animationId); //since the sound is stoped and animation finished, stop the requestAnimation to prevent potential memory leak,THIS IS VERY IMPORTANT!
185 | return;
186 | };
187 | };
188 | var step = Math.round(array.length / meterNum); //sample limited data from the total array
189 | ctx.clearRect(0, 0, cwidth, cheight);
190 | for (var i = 0; i < meterNum; i++) {
191 | var value = array[i * step];
192 | if (capYPositionArray.length < Math.round(meterNum)) {
193 | capYPositionArray.push(value);
194 | };
195 | ctx.fillStyle = capStyle;
196 | //draw the cap, with transition effect
197 | if (value < capYPositionArray[i]) {
198 | ctx.fillRect(i * 12, cheight - (--capYPositionArray[i]), meterWidth, capHeight);
199 | } else {
200 | ctx.fillRect(i * 12, cheight - value, meterWidth, capHeight);
201 | capYPositionArray[i] = value;
202 | };
203 | ctx.fillStyle = gradient; //set the filllStyle to gradient for a better look
204 | ctx.fillRect(i * 12 /*meterWidth+gap*/ , cheight - value + capHeight, meterWidth, cheight); //the meter
205 | }
206 | that.animationId = requestAnimationFrame(drawMeter);
207 | }
208 | this.animationId = requestAnimationFrame(drawMeter);
209 | },
210 | _audioEnd: function(instance) {
211 | if (this.forceStop) {
212 | this.forceStop = false;
213 | this.status = 1;
214 | return;
215 | };
216 | this.status = 0;
217 | var text = 'HTML5 Audio API showcase | An Audio Viusalizer';
218 | document.getElementById('fileWrapper').style.opacity = 1;
219 | document.getElementById('info').innerHTML = text;
220 | instance.info = text;
221 | document.getElementById('uploadedFile').value = '';
222 | },
223 | _updateInfo: function(text, processing) {
224 | var infoBar = document.getElementById('info'),
225 | dots = '...',
226 | i = 0,
227 | that = this;
228 | infoBar.innerHTML = text + dots.substring(0, i++);
229 | if (this.infoUpdateId !== null) {
230 | clearTimeout(this.infoUpdateId);
231 | };
232 | if (processing) {
233 | //animate dots at the end of the info text
234 | var animateDot = function() {
235 | if (i > 3) {
236 | i = 0
237 | };
238 | infoBar.innerHTML = text + dots.substring(0, i++);
239 | that.infoUpdateId = setTimeout(animateDot, 250);
240 | }
241 | this.infoUpdateId = setTimeout(animateDot, 250);
242 | };
243 | }
244 | }
245 |
--------------------------------------------------------------------------------
/audio_visualizer_single_page_version.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | HTML5 Audio API showcase | Audio visualizer
6 |
28 |
29 |
30 |
31 |
32 |
33 | HTML5 Audio API showcase | An Audio Viusalizer
34 |
35 |
Drag&drop or select a file to play:
36 |
37 |
38 |
39 |
40 |
41 |
42 |
45 |
291 |
299 |
300 |
--------------------------------------------------------------------------------