├── index.html
├── styles
└── index.css
└── scripts
├── MusicVisualizer.js
└── index.js
/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | loosen's MusicVisualizer
6 |
7 |
8 |
9 |
10 |
21 |
22 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
--------------------------------------------------------------------------------
/styles/index.css:
--------------------------------------------------------------------------------
1 | html,body,div,header,h1,h2,footer,p,ul,li,span,a,dl,dt,dd,
2 | section,canvas,input,button,form,textarea,select{
3 | margin: 0;
4 | padding: 0;
5 | list-style: none;
6 | color: #fff;
7 | box-sizing: border-box;
8 | }
9 | a{
10 | text-decoration: none;
11 | }
12 | html,body {
13 | height: 100%;
14 | width: 100%;
15 | font: 14px "Lucida Grande", Helvetica, Arial, sans-serif;
16 | background-color: #000;
17 | color: #fff;
18 | text-align: center;
19 | overflow: hidden;
20 | }
21 | input[type='range'] {
22 | -webkit-appearance: none;
23 | background-color: gray;
24 | height: 8px;
25 | border-radius: 15px;
26 | outline: none;
27 | }
28 | /*滑块样式*/
29 | input[type='range']::-webkit-slider-thumb{
30 | -webkit-appearance: none;
31 | height: 12px;
32 | width: 12px;
33 | background: #ddd;
34 | border-radius: 100%;
35 | border: none;
36 | }
37 | /*滑条*/
38 | input[type='range']::-moz-range-track{
39 | height: 8px;
40 | background: gray;
41 | border-radius: 15px;
42 | border: none;
43 | }
44 | input[type='range']::-moz-range-thumb{
45 | height: 12px;
46 | width: 12px;
47 | background: #ddd;
48 | border: none;
49 | border-radius: 100%;
50 | }
51 | header,.right{
52 | /*布局采用绝对定位*/
53 | position: absolute;
54 | }
55 | header{
56 | left: 0px;
57 | top: 0px;
58 | right: 0px;
59 | height: 150px;
60 | z-index: 10;
61 | /*background-color: white;*/
62 | /*color: black;*/
63 | }
64 | header button#add{
65 | display: block;
66 | color: white;
67 | background-color: black;
68 | margin: 10px auto;
69 | height: 50px;
70 | font-size: 30px;
71 | border-radius: 5px;
72 | padding: 0 5px;
73 | }
74 | header input#loadfile{
75 | display: none;
76 | }
77 | ul.type{
78 | display: inline-block;
79 | border: solid #fff 1px;
80 | cursor: pointer;
81 | margin-bottom: 10px;
82 | border-radius: 5px;
83 | }
84 | ul.type li{
85 | float: left;
86 | height: 30px;
87 | line-height: 30px;
88 | width: 80px;
89 | }
90 | .right{
91 | /*top: 150px;*/
92 | top: 0px;
93 | left: 0px;
94 | bottom: 0px;
95 | right: 0px;
96 | }
97 | .selected{
98 | color: green;
99 | }
100 | .selectedType{
101 | color: #000;
102 | background-color: #fff;
103 | }
104 |
105 | /*媒体查询*/
106 | @media screen and (max-width:800px),screen and (max-height:500px){
107 | body{
108 | font-size: 12px;
109 | }
110 | header{
111 | height: 85px;
112 | }
113 | header h1{
114 | font-size: 24px;
115 | height: 34px;
116 | line-height: 34px;
117 | }
118 | header button#add{
119 | font-size: 20px;
120 | height: 25px;
121 | width: 40%;
122 | }
123 | ul.type{
124 | margin-bottom: 2px;
125 | }
126 | ul.type li{
127 | height: 19px;
128 | line-height: 19px;
129 | }
130 | .right{
131 | /*top:85px;*/
132 | }
133 | }
--------------------------------------------------------------------------------
/scripts/MusicVisualizer.js:
--------------------------------------------------------------------------------
1 | function Musicvisualizer(obj){
2 | this.source = null;
3 | this.count = 0;
4 |
5 | this.analyser = Musicvisualizer.ac.createAnalyser();
6 | this.size = obj.size;
7 | this.analyser.fftSize = this.size*2;
8 |
9 | // GainNode用来控制音频的音量
10 | this.gainNode = Musicvisualizer.ac[Musicvisualizer.ac.createGain?"createGain":"createGainNode"]();
11 | // 对象调用对象可以用obj.method,也可以obj[method]
12 | this.gainNode.connect(Musicvisualizer.ac.destination);
13 |
14 | this.analyser.connect(this.gainNode);
15 |
16 | this.xhr = new XMLHttpRequest();
17 | this.draw = obj.draw;
18 | this.visualize();
19 | }
20 |
21 | Musicvisualizer.ac = new (window.AudioContext || window.webkitAudioContext)();//共用的
22 |
23 | // 解决 Chrome 66之后高版本中AudioContext被强行suspend的问题
24 | if(typeof AudioContext != "undefined" || typeof webkitAudioContext != "undefined") {
25 | var resumeAudio = function() {
26 | if(typeof Musicvisualizer.ac == "undefined" || Musicvisualizer.ac == null) return;
27 | if(Musicvisualizer.ac.state == "suspended") Musicvisualizer.ac.resume();
28 | document.removeEventListener("click", resumeAudio);
29 | };
30 | document.addEventListener("click", resumeAudio);
31 | }
32 |
33 | // load -> decode -> play
34 | Musicvisualizer.prototype.load = function(url,fun){
35 | this.xhr.abort();
36 | this.xhr.open("GET",url);
37 | this.xhr.responseType = "arraybuffer";
38 | var self = this;
39 | this.xhr.onload = function(){
40 | fun(self.xhr.response);
41 | }
42 | this.xhr.send();
43 | }
44 |
45 | // BaseAudioContext.decodeAudioData()用来生成AudioBuffer
46 | // AudioBuffer供AudioBufferSourceNode使用,这样,AudioBufferSourceNode才可以播放音频数据
47 | Musicvisualizer.prototype.decode = function(arraybuffer,fun){
48 | Musicvisualizer.ac.decodeAudioData(arraybuffer,function(buffer){
49 | fun(buffer);
50 | },function(err){
51 | console.log(err);
52 | });
53 | }
54 |
55 | Musicvisualizer.prototype.play = function(path){
56 | var n = ++this.count;
57 | var self = this;
58 | self.source && self.source[self.source.stop ? "stop":"noteOff"](); // 开始前先暂停之前音频的播放,防止多份音频同时播放
59 | if(path instanceof ArrayBuffer){
60 | self.decode(path,function(buffer){
61 | if(n!=self.count) return;
62 | var bufferSource = Musicvisualizer.ac.createBufferSource();
63 | // 将解码成功后的buffer赋值给bufferSource的buffer属性
64 | bufferSource.buffer = buffer;
65 | bufferSource.loop = true;
66 | bufferSource.connect(self.analyser);
67 | bufferSource[bufferSource.start?"start":"noteOn"](0);
68 | self.source = bufferSource;
69 | });
70 | }
71 | else{
72 | self.load(path,function(arraybuffer){
73 | if(n!=self.count) return;
74 | self.decode(arraybuffer,function(buffer){
75 | if(n!=self.count) return;
76 | var bufferSource = Musicvisualizer.ac.createBufferSource();
77 | // 将解码成功后的buffer赋值给bufferSource的buffer属性
78 | bufferSource.buffer = buffer;
79 | bufferSource.connect(self.analyser);
80 | bufferSource[bufferSource.start?"start":"noteOn"](0);
81 | self.source = bufferSource;
82 | });
83 | });
84 | }
85 |
86 | }
87 |
88 | Musicvisualizer.prototype.changeVolumn = function(percent){
89 | this.gainNode.gain.value = percent * percent;
90 | }
91 |
92 | Musicvisualizer.prototype.visualize = function(){
93 | var self = this;
94 | var arr = new Uint8Array(self.analyser.frequencyBinCount);//数组长度是fftsize的一半
95 | // console.log(self.analyser.frequencyBinCount)
96 | requestAnimationFrame = window.requestAnimationFrame ||
97 | window.webkitrequestAnimationFrame ||
98 | window.mozrequestAnimationFrame;//兼容
99 | function fn(){
100 | self.analyser.getByteFrequencyData(arr);// 将音频频域数据复制到传入的Uint8Array数组
101 | self.draw(arr);
102 | requestAnimationFrame(fn);
103 | }
104 | requestAnimationFrame(fn);
105 | }
106 |
--------------------------------------------------------------------------------
/scripts/index.js:
--------------------------------------------------------------------------------
1 | function $(s){
2 | return document.querySelectorAll(s);
3 | }
4 |
5 | var size = 128;//定义的音频数组长度
6 |
7 | var box = $('.right')[0];
8 | var canvas = document.createElement("canvas");
9 | var ctx = canvas.getContext("2d");
10 | var line;//渐变色变量
11 | box.appendChild(canvas);
12 | var height,width;
13 | var Dots = [];//用于存放点对象数组,点的坐标和颜色信息
14 | var list = $("#list li");
15 |
16 | var mv = new Musicvisualizer({
17 | size:size,
18 | draw:draw
19 | });
20 |
21 | // 歌曲点击切换
22 | // for(var i=0;i 10?10:cw;//防止上面矩形过高
100 | for(var i=0;i0 && o.cap height-capHeight ? height-capHeight:rectHeight+40;
113 | }
114 | }else if(draw.type == "dot"){
115 | ctx.beginPath();//声明,防止各个圆之间连线起来
116 | var r = 5+arr[i]/256*(height>width?height:width)/12;//圆的半径 最小10px,并且半径大小会依赖屏幕的宽度大小
117 | ctx.arc(o.x,o.y,r,0,Math.PI*2,true);//x,y,半径,起始角度,绘制角度,是否逆时针
118 | var round = ctx.createRadialGradient(o.x,o.y,0,o.x,o.y,r);//从圆心到圆最外围
119 | round.addColorStop(0,"#fff");
120 | round.addColorStop(1,o.color);
121 | ctx.fillStyle = round;
122 | ctx.fill();
123 | // ctx.strokeStyle = round;
124 | // ctx.stroke();
125 | o.x += o.dx;
126 | o.x = o.x > width ? 0 : o.x;
127 | }
128 |
129 | }
130 | }
131 | draw.type = "column";//默认显示效果类型
132 |
133 | $("#add")[0].onclick = function(){
134 | $("#loadfile")[0].click();
135 | }
136 |
137 | $("#loadfile")[0].onchange = function(){
138 | var file = this.files[0];
139 | var fr = new FileReader();
140 |
141 | fr.onload = function(e){
142 | // 重写play方法 这边e.target.result已经是arraybuffer对象类型,不再是ajax路径读入
143 | mv.play(e.target.result);
144 | }
145 | fr.readAsArrayBuffer(file);
146 | // $("#loadfile")[0].value = '';
147 | }
148 |
--------------------------------------------------------------------------------