Tự Code một trình phát nhạc cho người dùng

5

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.

Download

5 Comments
  1. HÁ TRỊNH nói

    mình muốn tạo play list trên blogspot tương thích với mobile bạn có code nào hợp lý có thể giới thiệu mình đc không

  2. Le Hai Phong nói

    code tạo 1 album ntn nhỉ, bác chỉ cho e được ko 

  3. Sơnn Smith nói

    Anh up lại file cho em với 😀

    1. Lại Văn Đức nói

      Đã up lại nhé bạn.

  4. Hoàng nói

    Anh up lại file cho em với ^_^

Để lại một trả lời

Địa chỉ email của bạn sẽ không được công bố.