DEV Community

Cover image for Make Music Player Using JavaScript
Nguyen Anh Vu
Nguyen Anh Vu

Posted on • Edited on • Originally published at homiedev.com

Make Music Player Using JavaScript

Giới thiệu

Trong bài hướng dẫn này chúng ta sẽ được luyện tập cách sử dụng lớp (class) trong JavasScript. Với những bạn mới học thì chúng ta sẽ tiếp cận thêm nhiều thứ hay ho. Cùng theo dõi bài viết này nhé 😄🔥

Source: here

Thực hành

Đầu tiên chúng ta sẽ xem thử demo Music Player này hoạt động như thế nào nhé 👇:

Xem hướng dẫn tại Homiedev

Các bạn đã xem qua video chúng ta sẽ thấy Music Player của chúng ta sẽ có những chức năng chính sau:

#Nút play/pause song

#Nút set progress song (tua bài hát)

#Chức năng chọn bài kế tiếp hoặc trước đó

#Chọn bài hát trong danh sách phát

Chúng ta sẽ cùng đi xây dựng các chức năng chính này nhé 😄😄.

Để bắt đầu thì chúng ta sẽ tạo HTML nhé:

Trong div container:

Chúng ta sẽ tạo một box chứa info song và các button play/pause với class là music-content.

<div class="music-content">
  <button class="play-list">Playlist <i class="fas fa-list"></i></button>
  <section class="music-content-box">
    <div class="thumbnail-song"><img src="image/MINHANH.jpg" alt="" /></div>
    <div class="content-wrapper">
      <div class="info-song">
        <p class="song-name">INI - Eluveitie</p>
        <p class="author">Eluvi</p>
      </div>
    </div>
  </section>
  <audio id="audio"></audio>
  <div class="bar-song">
    <span class="current-time">00:00</span>
    <div class="progress"><div class="progress-bar"></div></div>
    <span class="duration-time">03:22</span>
  </div>
  <div class="song-footer">
    <button class="back"><i class="fas fa-step-backward"></i></button>
    <button class="play-song"><i class="fas fa-play"></i></button>
    <button class="forward"><i class="fas fa-step-forward"></i></button>
  </div>
</div>
Enter fullscreen mode Exit fullscreen mode

Dưới div music-content, ta tạo một div chứa danh sách các bài hát:

<div class="playlist-box">
  <div class="header">
    <button class="button go-home">
      <i class="fas fa-chevron-left"></i>
    </button>
    <div class="text"><p>Playlist</p></div>
  </div>
  <div class="list-song"></div>
</div>
Enter fullscreen mode Exit fullscreen mode

Xong phần html, đến phần css mình sẽ show các thuộc tính quan trọng liên quan đến phần javascript. Các bạn có thể tự design hoặc toàn bộ sorce mình sẽ để ở đầu bài viết.

Chúng ta sẽ ẩn phần playlist-box đi, mình muốn khi click vào button thì div này mới hiện lên.

.playlist-box {
    ...
    opacity: 0;
    visibility: hidden;
    transform: scale(1.1);
}
.playlist-box.active {
    opacity: 1;
    visibility: visible;
    transform: scale(1);
}
Enter fullscreen mode Exit fullscreen mode

https://codesandbox.io/s/make-music-player-with-vanilla-javascript-wh7yt?file=/index.html

Tiếp theo ta sẽ đến phần JavaScript nhé 😁😁

👉 Đầu tiên ta cùng tạo một danh sách các bài hát nhé.

const listMusic = [
    { song: "When I'm Gone", author: 'Eminem' },
    { song: 'Mockingbird', author: 'Eminem' },
    { song: 'Ghetto Gospel', author: 'Tupac' },
    { song: 'Still Love You', author: 'Tupac' },
];
Enter fullscreen mode Exit fullscreen mode

Mình đã import các bài hát cùng với tên như ở trên vào folder music

Sau khi tạo xong, ta sẽ thêm một class có tên là UI. Mình sẽ dùng cú pháp trong ECMAScript 2015 để tạo class. Class này chứa các method để ta tác động lên DOM mà mình sẽ khởi tạo ngay sau đây.

class UI {
  constructor() {
    this.songIndex = 0;
  }
  // method
}
Enter fullscreen mode Exit fullscreen mode

Trong class này chứa một constructor chứa vị trí bài hát trong danh sách là this.songIndex = 0.

Tiếp tục, ta sẽ chuyển đến phần method:

👉 Method đầu tiên ta cần tạo là hàm để xử lí nhiệm vụ show playlist, nó sẽ hiện danh sách bài hát khi click vào button tương ứng. Tương tự ta sẽ có method để hide playlist.

class UI {
    constructor() {
        this.songIndex = 0;
    }

    // show playlist
    showPlayListBox() {
        playListBox.classList.add('active');
    }
    // hide playlist
    hidePlayListBox() {
        playListBox.classList.remove('active');
    }
}
Enter fullscreen mode Exit fullscreen mode

👉 Method tiếp theo, ta dùng để load info song khi trang của chúng ta load xong.

const audio = document.querySelector('#audio');
class UI {
  // load detail song when page loaded
  loadSong(music) {
    audio.src = `music/${music.song}.mp3`;

    this.getDuration(audio).then((time) => {
      thumbnailSong.src = `image/${music.song}.jpg`;
      nameSong.textContent = music.song;
      author.textContent = music.author;
      timeSong.textContent = time;
      thumbnailSong.classList.add('rotate-ani');
    });
  }
}
Enter fullscreen mode Exit fullscreen mode

Đầu vào là object, ví dụ: { song: "When I'm Gone", author: 'Eminem' }.

Tuy nhiên, khi làm tới method này, ta gặp phải một vấn đề đó là khi get time của bài hát console.log sẽ hiển thị giá trị NaN.

Chúng ta sẽ lấy time của bài hát theo cách này:

const audio = document.querySelector('#audio');
const time = audio.duration; // NaN
Enter fullscreen mode Exit fullscreen mode

Ta nhận được giá trị NaN đó là bởi vì audio cần thời gian để load và nó chưa sẵn sàng khi ta gọi. Để giải quyết vấn đề này ta sẽ dùng event loadedmetadata nó sẽ đượcc kích hoạt khi metadata đã được tải.

Mình sẽ tạo một method để get duration là getDuration(music). Method này sẽ trả về một Promise chứa kết quả là time của bài hát.

class UI {
  getDuration(music) {
    return new Promise(function (resolve) {
      music.addEventListener('loadedmetadata', function () {
        const time = formatTime(music.duration);

        resolve(time);
      });
    });
  }
}
Enter fullscreen mode Exit fullscreen mode

Vì giá trị trả về của duration là seconds (giây) nên mình sẽ forrmat lại định dạng nhờ formatTime(seconds).

Chi tiết hàm formatTime:

function formatTime(sec_num) {
    let hours = Math.floor(sec_num / 3600);
    let minutes = Math.floor((sec_num - hours * 3600) / 60);
    let seconds = Math.floor(sec_num - hours * 3600 - minutes * 60);

    hours = hours < 10 ? (hours > 0 ? '0' + hours : 0) : hours;

    if (minutes < 10) {
        minutes = '0' + minutes;
    }
    if (seconds < 10) {
        seconds = '0' + seconds;
    }
    return (hours !== 0 ? hours + ':' : '') + minutes + ':' + seconds;
}
Enter fullscreen mode Exit fullscreen mode

Như vậy là chúng ta đã hiểu về method loadSong(). Ngoài ra trong method này mình còn add thêm animation rotate cho thumbnail của song.

Mặc định animation mình sẽ làm nó ngừng chuyển động bằng animation-play-state: paused;. Khi click play animation sẽ chuyển động tiếp 😄.

.thumbnail-song img.rotate-ani {
    animation: rotate 5s linear infinite;
    animation-play-state: paused;
}
@keyframes rotate {
    0% {
        transform: rotate(0);
    }
    100% {
        transform: rotate(360deg);
    }
}
Enter fullscreen mode Exit fullscreen mode

Như vậy là chúng ta đã xong method loadSong(), cũng khá dài dòng vì mình muốn giải thích cho các bạn hiểu rõ 😁

⚡ Cùng chuyển tới method tiếp theo nhé. Method này có nhiệm vụ thêm danh sách các bài hát vào DOM 👉.

class UI {
  // set list song
  async setSongs() {
    songs.innerHTML = '';

    for (let i = 0; i < listMusic.length; i++) {
      const music = new Audio(`music/${listMusic[i].song}.mp3`);
      const time = await this.getDuration(music);

      songs.insertAdjacentHTML(
        'beforeend',
        `<div class="song-info">
          <div class="left">
            <span class="name-song">${listMusic[i].song}</span>
            <span class="author">${listMusic[i].author}</span>
          </div>
          <div class="right">
            <span class="minutes">${time}</span>
          </div>
        </div>`
      );
    }
  }
}
Enter fullscreen mode Exit fullscreen mode

Giải thích một chút, lý do ở method này mình dùng async/await vì để lấy thời gian của các bài hát trong danh sách có sẵn mình đã dùng một Promise và trong for...loop lúc này là bất đồng bộ. Để xử lí vấn đề này mình đã tạo một async function. Các bạn có thể đọc thêm bài viết giải thích cách dùng async/await trong các vòng lặp tại đây

Sau khi lấy được thời lượng của mỗi bài hát ta sẽ thêm nó vào trong div songs chứa danh sách các bài hát.

😁🖐 Mọi chuyện đơn giản hơn khi ta xử lí vấn đề ở hai method trên, Tiếp theo ta sẽ xử lí cho button play. Khi click vào nhạc sẽ phát, đồng hơn các thông tin của bài hát sẽ hiện ra.

const musicContent = document.querySelector('.music-content');
const thumbnailSong = document.querySelector('.thumbnail-song img');
const btnPlay = document.querySelector('.play-song');

class UI {
  // play song
  playSong() {
    musicContent.classList.add('playing');
    thumbnailSong.style.animationPlayState = 'running';
    btnPlay.querySelector('.fas').classList.remove('fa-play');
    btnPlay.querySelector('.fas').classList.add('fa-pause');

    audio.play();
  }
}
Enter fullscreen mode Exit fullscreen mode

Khi nút play được kích hoạt, mình cho animation tiếp tục hoạt động. Và sửa icon button play thành icon button pause.

Mình thêm một class cho musicContent để kiểm tra trang thái play or pause của bài hát. Tí nữa mình sẽ giải thích ^^

Tương tự với button pause:

// pause song
pauseSong() {
  musicContent.classList.remove('playing');
  thumbnailSong.style.animationPlayState = 'paused';
  btnPlay.querySelector('.fas').classList.add('fa-play');
  btnPlay.querySelector('.fas').classList.remove('fa-pause');

  audio.pause();
}
Enter fullscreen mode Exit fullscreen mode

👉 Method tiếp theo sẽ thực hiện chuyển bài hát tiếp theo khi click vào button tương ứng. method này khá đơn giản nên mình sẽ không giải thích nhiều nữa.

// next song
nextSong() {
  this.songIndex++;

  if (this.songIndex > listMusic.length - 1) {
    this.songIndex = 0;
  }

  this.loadSong(listMusic[this.songIndex]);
}
// prev song
prevSong() {
  this.songIndex--;

  if (this.songIndex < 0) {
    this.songIndex = listMusic.length - 1;
  }

  this.loadSong(listMusic[this.songIndex]);
}
Enter fullscreen mode Exit fullscreen mode

🔥 Chúng ta đã đi được gần hết các method để xử lí những yêu cầu mà chúng ta đặt ra, Method tiếp theo cũng khá hay đấy, hãy tiếp tục theo dõi nhé 😁

👉 Method updateProgress(e):

Mình sẽ update lại width sau mỗi giây bài hát cho thanh bar progress và hiển thị thời gian hiện tại của bài hát.

const progressBar = document.querySelector('.progress-bar');
const currentTimeDisplay = document.querySelector('.current-time');
// update progress
class UI {
  // update progress
  updateProgress(e) {
    const { currentTime, duration } = e.srcElement;
    const percentWidth = (currentTime / duration) * 100;
    progressBar.style.width = `${percentWidth}%`;
    const time = formatTime(currentTime);

    currentTimeDisplay.textContent = time;
  }
}
Enter fullscreen mode Exit fullscreen mode

👉 Method setProgress(e).

Ở method này ta giả sử người dùng muốn tua đoạn nhạc đến đoạn mong muốn. Ta sẽ update lại thanh progress đồng thời set lại currentTime tương ứng.

Ta sẽ tìm vị trí của click chuột thông qua const width = e.offsetX; ta sẽ tìm được width của thanh progress bar.

Sau đó, set lại width cho thanh progressBar và update lại thời gian hiện tại của bài hát.

const progressBar = document.querySelector('.progress-bar');
const audio = document.querySelector('#audio');
class UI {
  // set progress
  setProgress(e) {
    const width = e.offsetX;
    const progress = e.currentTarget;
    const progressBarWidth = (width / progress.clientWidth) * 100;
    progressBar.style.width = `${progressBarWidth}%`;

    let { duration } = audio;
    audio.currentTime = (width * duration) / progress.clientWidth;
  }
}
Enter fullscreen mode Exit fullscreen mode

👉 Method chọn bài hát trong playlist.

Ở method này mình sẽ tìm bài hát tương ứng trong listMusic khi ta click chuột vào bài hát trong danh sách phát.

class UI {
  // select song in playlist
  selectSong(e) {
    const target = e.target;

    const nameSong = target.querySelector('.name-song').textContent;
    const song = listMusic.find((audio) => audio.song === nameSong);

    this.loadSong(song);
    this.playSong();

    this.hidePlayListBox();
  }
}
Enter fullscreen mode Exit fullscreen mode

🔥🔥🔥 Như vậy là ta đã hoàn thành xong class UI và các method của nó. Công việc tiếp theo là mang đi sử dụng thôi ^^.

Mình sẽ tạo một event sẽ được chạy khi page load xong.

document.addEventListener('DOMContentLoaded', eventListeners);
function eventListeners() {
  ...
}
Enter fullscreen mode Exit fullscreen mode

Trong eventListeners, ta cùng thêm các event và method để sử dụng, Mình sẽ tạo một đối tượng của class UI.

  const ui = new UI();
Enter fullscreen mode Exit fullscreen mode

Đầu tiên, load bài hát và danh sách bài hát:

function eventListeners() {
  const ui = new UI();

  // load song
  ui.loadSong(listMusic[ui.songIndex]);
  // handle set list song
  ui.setSongs();
}
Enter fullscreen mode Exit fullscreen mode

Xử lí open/close danh sách phát:

// handle show playlist
btnPlayList.addEventListener('click', function () {
  ui.showPlayListBox();
});
// handle hide playlist
btnHome.addEventListener('click', function () {
  ui.hidePlayListBox();
});
Enter fullscreen mode Exit fullscreen mode

Xử lí play/pause song:

Mình sẽ kiểm tra musicConent chứa hay không class playing để thực hiện chuyển đổi button. Đây là lí do mình add class playing ở method playSong() và pauseSong().

// play/pause song
btnPlay.addEventListener('click', function () {
  if (musicContent.classList.contains('playing')) {
    ui.pauseSong();
  } else {
    ui.playSong();
  }
});
Enter fullscreen mode Exit fullscreen mode

Xử lí cho thanh progress:

// update progress
audio.addEventListener('timeupdate', function (e) {
  ui.updateProgress(e);
});
// set progress
progress.addEventListener('click', function (e) {
  ui.setProgress(e);
});
Enter fullscreen mode Exit fullscreen mode

Xử lí cho button next hoặc lùi bài hát:

// previous song
btnBack.addEventListener('click', function () {
  ui.prevSong();
  ui.playSong();
});
// forward song
btnForward.addEventListener('click', function () {
  ui.nextSong();
  ui.playSong();
});
Enter fullscreen mode Exit fullscreen mode

Chọn bài hát trong danh sách phát:

// select song
songs.addEventListener('click', function (e) {
  ui.selectSong(e);
});
Enter fullscreen mode Exit fullscreen mode

Cuối cùng là xử lí khi bài hát kết thúc:

// end song
audio.addEventListener('ended', function () {
  ui.nextSong();
  ui.playSong();
});
Enter fullscreen mode Exit fullscreen mode

Xem toàn bộ source: Làm Music Player sử dụng JavaScript

Kết luận

Hi vọng với bài hướng dẫn này các bạn sẽ học thêm được những kiến thức bổ ích 😁😁.

Chúng ta sẽ gặp lại nhau trong những bài viết hay ho sắp tới nhé 🔥.

Các bạn có thể đọc bài viết gốc tại website của mình: Homiedev.com

Top comments (1)

Collapse
 
ethanbrook4311 profile image
Ethan Brooks

Wow! That's great. As an avid music enthusiast and frequent visitor to recording studios, I recently had the pleasure of experiencing UV Studio studiorecordingchicago.com in Chicago and I must say, it exceeded all my expectations. From the moment I walked through the doors, I knew I was in for something special. The studio boasts state-of-the-art equipment and a spacious layout that is acoustically impeccable. The attention to detail in every aspect of the studio's design is truly commendable, and it's clear that no expense was spared in creating an environment conducive to creativity and innovation.

Some comments may only be visible to logged-in visitors. Sign in to view all comments.