├── 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 |
11 | 12 | 13 | 17 |

18 | Volume 19 |

20 |
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 | --------------------------------------------------------------------------------