Tự Code một trình phát nhạc cho người dùng
Cái tiêu đề có vẻ củ chuối phải không các bạn. Cụ thể, trong bài viết này, tôi sẽ trình bày một trình phát nhạc mà bạn có thể sử dụng bằng cách upload file nhạc của bạn lên để nghe trong trường hợp máy tính của bạn không có trình phát nhạc.
Nếu như các bài viết trước bạn sẽ thấy Code trước khi Demo thì tại bài viết này chúng ta sẽ Demo trước cho bạn dễ hiểu sau đó chúng ta nói đến Code sau. Bây giờ các bạn hãy vào website nào đó mà bạn hay nghe nhạc rồi tiến hành downlaod một bài về. Sau đó sử dụng công cụ dưới đây để thưởng thức bài nhạc đó.
See the Pen Self Code a music player for users by Lại Văn Đức (@laivanduc) on CodePen.16775
Ứng dụng này không tải file nhạc của bạn lên mà sử dụng chính file nhạc đó để phát mà thôi. Và dưới đây là toàn bộ Code của chúng ta sẽ cần đến:
Đầu tiên các bạn phải gọi file JS này về:
<script src='http://cdnjs.cloudflare.com/ajax/libs/dat-gui/0.5/dat.gui.min.js'></script>
Sau đó là Code HTML Jade để làm khung cho ứng dụng này:
.musicControls input.audiofile(type='file') //- button.listenButton ...or listen to one of mine // Silly CORS button.playPauseButton ▶ i#loading Select an audio file... // This should match the values in the js - var maxSideNum = 24 - var maxRectangleNum = 24 .prism - for (var x = 0; x < maxSideNum; x++) .side.hide - for (var y = 0; y < maxRectangleNum; y++) .rectangle.hide
Và đây là đoạn CSS chúng ta cần tới:
// All of the commented out things are from the static, pre-dat.gui version that are no longer necessary //$sideNum: 12; //$rectNum: 12; //$width: 100px; //$margin: 1% * 7.3; $border: 3px solid #4DA16F; /* Default all same color */ * { box-sizing: border-box; } html, body { height:100%; } body { background: rgb(30,30,30); font-family: 'Helvatica', sans-serif; color: #FFF; position:relative; perspective: 1000px; perspective-origin: 50% 50%; } .hide { display:none; } .prism { position:absolute; //top:calc(50% - #{$width * 2}); //left:calc(50% - #{$width * 0.5}); //width:$width; //height:$width * 4; transform-style: preserve-3d; animation:rotate 8s linear infinite; display:none; } .side { width:100%; height:100%; border-top: $border; border-bottom: $border; border-color:currentColor; position:absolute; } .rectangle { //height: (100% / $rectNum) - 2%; //width:80%; //margin:10px auto; border: $border; border-color:currentColor; } .solid .rectangle { background:currentColor; } //@for $i from 1 through $sideNum { // .side:nth-child(#{$i}) { // color: hsl(144, 55%, 20% + ($i / $sideNum) * 40%); // // transform: rotateY($i * (360 / $sideNum) + deg) translateZ($width * 1.85) rotateX(180deg); // } // .side.rainbow:nth-child(#{$i}) { // color: hsl(360*($i / $sideNum), 80%, 55%); // } //} @keyframes rotate { from { transform:rotateY(0); } to { transform:rotateY(-360deg); } } .musicControls, i { position: absolute; border-radius: 5px; border: 1px solid rgba(255, 255, 255, 0.3); padding: 10px; } .musicControls { top: 20px; left: 20px; } i { left: 50%; top: 50%; transform: translateY(-50%) translateX(-50%); } body.loaded { #loading { display: none; } .prism { display:block; } }
Cuối cùng là Code JavaScript:
var maxSideNum = 24, maxRectangleNum = 24; // Dat.gui setup var Options = function() { this.height = 400; this.radius = 185; this.sideCount = 12; this.rectangleCount = 12; this.rectangleWidth = 80; this.vertMargin = 10; this.borderWidth = 3; this.color = 200; this.solidBG = false; this.rainbowMode = false; this.animateThroughSpectrum = false; }; window.onload = function() { // dat.gui setup var myOptions = new Options(), gui = new dat.GUI(), f1 = gui.addFolder('Prism Controls'), f2 = gui.addFolder('Rectangle Controls'), f3 = gui.addFolder('Color Controls'), mySideCount = f1.add(myOptions, 'sideCount', 3, maxSideNum).step(1), myRadius = f1.add(myOptions, 'radius', 30, 600).step(15), myHeight = f1.add(myOptions, 'height', 50, 750).step(50), myRectangleCount = f2.add(myOptions, 'rectangleCount', 3, maxRectangleNum).step(1), myRectangleWidth = f2.add(myOptions, 'rectangleWidth', 1, 100).step(5), myVertMargin = f2.add(myOptions, 'vertMargin', 0, 15).step(1), myBorderWidth = f2.add(myOptions, 'borderWidth', 0, 15).step(1), myColor = f3.add(myOptions, 'color', 0, 360).step(1), mySolidBG = f3.add(myOptions, 'solidBG'), myRainbow = f3.add(myOptions, 'rainbowMode'), myAnimateThroughSpectrum = f3.add(myOptions, 'animateThroughSpectrum'); f2.open(); var audio, analyser, audioctx, sourceNode, stream; var audioInput = document.querySelector('.audiofile'), listenButton = document.querySelector(".listenButton"), playPauseButton = document.querySelector(".playPauseButton"); var c = 0, // Used to change color over time paused = true; /*var myMusic = [ "http://zachsaucier.com/music/Initiation.mp3", "http://zachsaucier.com/music/High%20Tide.mp3", "http://zachsaucier.com/music/Dolphin%20Style.mp3", "http://zachsaucier.com/music/King.mp3" ];*/ var prism = document.querySelector(".prism"), sides = document.querySelectorAll(".side"), rectangleArray = [maxSideNum], lastTime = Date.now(), timeGap = 50; function rectangleSetup() { for(var i = 0; i < maxSideNum; i++) { rectangleArray[i] = sides[i].querySelectorAll(".rectangle"); } } rectangleSetup(); // dat.gui listeners // f1 listeners function sideCountChange(newCount) { [].forEach.call(sides, function(elem, i) { if(i < myOptions.sideCount) { // The circle is inscribed inside of the prism, so we can use this formula to calculate the side length var sideLength = 2 * (myOptions.radius) * Math.tan(Math.PI / newCount); prism.style.width = sideLength + "px"; prism.style.left = "calc(50% - " + sideLength / 2 + "px)"; sides[i].style.transform = "rotateY(" + i * (360 / newCount) + "deg) translateZ(" + myOptions.radius + "px) rotateX(180deg)"; sides[i].classList.remove("hide"); } else { sides[i].classList.add("hide"); } }); } mySideCount.onFinishChange(sideCountChange); sideCountChange(myOptions.sideCount); function radiusChange(newRadius) { sideCountChange(myOptions.sideCount); } myRadius.onFinishChange(radiusChange); radiusChange(myOptions.radius); function heightChange(newHeight) { prism.style.height = newHeight + "px"; prism.style.top = "calc(50% - " + newHeight / 2 + "px)" rectangleCountChange(myOptions.rectangleCount); } myHeight.onFinishChange(heightChange); heightChange(myOptions.height); // f2 listeners function rectangleCountChange(newCount) { [].forEach.call(rectangleArray, function(side, i) { [].forEach.call(side, function(rect, i) { if(i < myOptions.rectangleCount) { rect.style.height = (myOptions.height - myOptions.vertMargin) / newCount - myOptions.vertMargin + "px"; rect.classList.remove("hide"); } else { rect.classList.add("hide"); } }); }); } myRectangleCount.onFinishChange(rectangleCountChange); rectangleCountChange(myOptions.rectangleCount); function rectangleWidthChange(newWidth) { [].forEach.call(rectangleArray, function(side, i) { [].forEach.call(side, function(rect, i) { rect.style.width = newWidth + "%"; }); }); } myRectangleWidth.onFinishChange(rectangleWidthChange); rectangleWidthChange(myOptions.rectangleWidth); function vertMarginChange(newMargin) { [].forEach.call(rectangleArray, function(side, i) { [].forEach.call(side, function(rect, i) { rect.style.margin = newMargin + "px auto"; }); }); rectangleCountChange(myOptions.rectangleCount); } myVertMargin.onFinishChange(vertMarginChange); vertMarginChange(myOptions.vertMargin); function borderWidthChange(newWidth) { [].forEach.call(rectangleArray, function(side, i) { [].forEach.call(side, function(rect, i) { rect.style.borderWidth = newWidth + "px"; }); }); } myBorderWidth.onFinishChange(borderWidthChange); borderWidthChange(myOptions.borderWidthChange); // f3 listeners function colorChange(value) { if(!myOptions.rainbowMode) [].forEach.call(sides, function(elem, i) { sides[i].style.color = "hsl(" + value + ", 55%, " + (20 + (i / myOptions.sideCount) * 40) + "%)"; }); } myColor.onFinishChange(colorChange); colorChange(myOptions.color); mySolidBG.onFinishChange(function(value) { if(value === true) prism.classList.add("solid"); else prism.classList.remove("solid"); }); function goRainbowMode(value) { [].forEach.call(sides, function(elem, i) { if(value === true) sides[i].style.color = "hsl(" + 360 * (i / myOptions.sideCount) + ", 80%, 55%)"; else colorChange(myOptions.color); }); } myRainbow.onFinishChange(goRainbowMode); function checkAnimateThroughSpectrum() { if(myOptions.animateThroughSpectrum) [].forEach.call(sides, function(elem, i) { sides[i].style.color = "hsl(" + c + ", 80%, " + (20 + (i / myOptions.sideCount) * 40) + "%)"; }); else if(myOptions.rainbowMode) goRainbowMode(true); else colorChange(myOptions.color); } // The music player listeners audioInput.addEventListener('change', function(event) { if(event.target.files[0]) { // No error checking of file here, could be added stream = URL.createObjectURL(event.target.files[0]); loadSong(stream); } }, false); if(listenButton) listenButton.addEventListener('click', chooseOneOfMine, false); playPauseButton.addEventListener('click', togglePlayPause, false); // The music functions function setup() { // Stop the previous song if there is one if(audio) togglePlayPause(); audio = new Audio(); audioctx = new AudioContext(); analyser = audioctx.createAnalyser(); analyser.smoothingTimeConstant = 0.75; analyser.fftSize = 512; audio.addEventListener('ended', songEnded, false); sourceNode = audioctx.createMediaElementSource(audio); sourceNode.connect(analyser); sourceNode.connect(audioctx.destination); } function loadSong(stream) { setup(); audio.src = stream; togglePlayPause(); document.body.classList.add('loaded'); update(); } function songEnded() { document.body.classList.remove('loaded'); togglePlayPause(); } function togglePlayPause() { if(paused) { document.body.classList.add('loaded'); audio.play(); playPauseButton.innerText = "▮▮"; } else if(!audio.paused && !audio.ended) { audio.pause(); playPauseButton.innerText = "▶"; } paused = !paused; } function chooseOneOfMine() { var num = Math.round(Math.random() * (myMusic.length - 1)) + 1; loadSong(myMusic[num]); } // The drawing functions function drawSide(freqSequence, freqPercent) { // Get the number of rectangles based on the freqValue drawRectangles(freqSequence, Math.floor(freqPercent * myOptions.rectangleCount / 100)) } function drawRectangles(sideNum, numRectanglesShowing) { for(var i = 0; i < myOptions.rectangleCount; i++) { if(i <= numRectanglesShowing) { rectangleArray[sideNum][i].classList.remove("hide"); } else { rectangleArray[sideNum][i].classList.add("hide"); } } } var sectionsAveraged = [maxSideNum], countSinceLast = [maxSideNum]; function update() { var currTime = Date.now(); var freqArray = new Uint8Array(analyser.frequencyBinCount); analyser.getByteTimeDomainData(freqArray); // Find the average of the values near to each other (grouping) var average = 0, count = 0, numPerSection = 256 / (myOptions.sideCount + 1), nextSection = numPerSection; for (var i = 0; i < freqArray.length; i++) { var v = freqArray[i]; average += Math.abs(128 - v); // 128 is essentially 0 count++; if(i > nextSection) { var currentSection = Math.floor(i / numPerSection - 1); sectionsAveraged[currentSection] += average / count; countSinceLast[currentSection]++; average = 0; count = 0; nextSection += numPerSection; } } // Find the average of the values since the last time checked per section (smoothing) if(currTime - lastTime > timeGap) { for (var i = 0; i < myOptions.sideCount; i++) { drawSide(i, (sectionsAveraged[i] / countSinceLast[i]), c); sectionsAveraged[i] = 0; countSinceLast[i] = 0; } lastTime = currTime; } checkAnimateThroughSpectrum(); c += 0.5; requestAnimationFrame(update); } };
Để dễ hiểu và bạn có thể áp dụng luôn vào các dự án thiết kế của mình. Bên dưới là Link Download toàn bộ ứng dụng Web này. Chúc các bạn thành công.