Make Music Player Using JavaScript
Nguyen Anh Vu
Posted on September 13, 2021
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>
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>
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);
}
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' },
];
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à 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');
}
}
👉 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ụ: { 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
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 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);
}
}
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>`
);
}
}
}
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();
}
}
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();
}
👉 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]);
}
🔥 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;
}
}
👉 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;
}
}
👉 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();
}
}
🔥🔥🔥 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() {
...
}
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();
Đầ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();
}
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();
});
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();
}
});
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);
});
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();
});
Chọn bài hát trong danh sách phát:
// select song
songs.addEventListener('click', function (e) {
ui.selectSong(e);
});
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();
});
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
Posted on September 13, 2021
Join Our Newsletter. No Spam, Only the good stuff.
Sign up to receive the latest update from our blog.
Related
May 4, 2022
December 26, 2020