Hướng dẫn music player html - máy nghe nhạc html

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 code: 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é 👇:

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 - Eluveitiep>
        <p class="author">Eluvip>
      div>
    div>
  section>
  <audio id="audio">audio>
  <div class="bar-song">
    <span class="current-time">00:00span>
    <div class="progress"><div class="progress-bar">div>div>
    <span class="duration-time">03:22span>
  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>

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>Playlistp>div>
  div>
  <div class="list-song">div>
div>

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ộ source code 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);
}

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' },
];

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
}

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

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

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');
    }
}

👉 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');
    });
  }
}

Đầu vào là object, ví dụ:

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

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

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);
      });
    });
  }
}

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;
}

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

.playlist-box {
    ...
    opacity: 0;
    visibility: hidden;
    transform: scale(1.1);
}
.playlist-box.active {
    opacity: 1;
    visibility: visible;
    transform: scale(1);
}
8. Khi click play animation sẽ chuyển động tiếp 😄.

<div class="playlist-box">
  <div class="header">
    <button class="button go-home">
      <i class="fas fa-chevron-left">i>
    button>
    <div class="text"><p>Playlistp>div>
  div>
  <div class="list-song">div>
div>
0

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 👉.

<div class="playlist-box">
  <div class="header">
    <button class="button go-home">
      <i class="fas fa-chevron-left">i>
    button>
    <div class="text"><p>Playlistp>div>
  div>
  <div class="list-song">div>
div>
1

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.

<div class="playlist-box">
  <div class="header">
    <button class="button go-home">
      <i class="fas fa-chevron-left">i>
    button>
    <div class="text"><p>Playlistp>div>
  div>
  <div class="list-song">div>
div>
2

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:

<div class="playlist-box">
  <div class="header">
    <button class="button go-home">
      <i class="fas fa-chevron-left">i>
    button>
    <div class="text"><p>Playlistp>div>
  div>
  <div class="list-song">div>
div>
3

👉 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.

<div class="playlist-box">
  <div class="header">
    <button class="button go-home">
      <i class="fas fa-chevron-left">i>
    button>
    <div class="text"><p>Playlistp>div>
  div>
  <div class="list-song">div>
div>
4

🔥 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

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

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.

<div class="playlist-box">
  <div class="header">
    <button class="button go-home">
      <i class="fas fa-chevron-left">i>
    button>
    <div class="text"><p>Playlistp>div>
  div>
  <div class="list-song">div>
div>
5

👉 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 listMusic = [
    { song: "When I'm Gone", author: 'Eminem' },
    { song: 'Mockingbird', author: 'Eminem' },
    { song: 'Ghetto Gospel', author: 'Tupac' },
    { song: 'Still Love You', author: 'Tupac' },
];
0 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.

<div class="playlist-box">
  <div class="header">
    <button class="button go-home">
      <i class="fas fa-chevron-left">i>
    button>
    <div class="text"><p>Playlistp>div>
  div>
  <div class="list-song">div>
div>
6

👉 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.

<div class="playlist-box">
  <div class="header">
    <button class="button go-home">
      <i class="fas fa-chevron-left">i>
    button>
    <div class="text"><p>Playlistp>div>
  div>
  <div class="list-song">div>
div>
7

🔥🔥🔥 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.

<div class="playlist-box">
  <div class="header">
    <button class="button go-home">
      <i class="fas fa-chevron-left">i>
    button>
    <div class="text"><p>Playlistp>div>
  div>
  <div class="list-song">div>
div>
8

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.

Đầu tiên, load bài hát và danh sách 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>Playlistp>div>
  div>
  <div class="list-song">div>
div>
9

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

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

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().

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

Xử lí cho thanh progress:

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

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

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

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

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

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

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

Nếu làm thành công chúng ta sẽ như demo này:

Xem toàn bộ source tại đây:

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é 🔥.