Leeyanggoo
[JS] 자바스크립트 뮤직 플레이어 Music Player 만들기!! <Audio> Method() / Event / Property 본문
[JS] 자바스크립트 뮤직 플레이어 Music Player 만들기!! <Audio> Method() / Event / Property
Leeyanggoo 2023. 4. 27. 23:56
HTML
<div class="music__wrap">
<div class="music__inner">
<div class="music__header">
<span class="left"></span>
<h2>MUSIC PLAYER</h2>
<span class="right"></span>
</div>
<div class="music__contents">
<div class="music__view">
<div class="image">
<img src="img/music_view01_01.png" alt="음악">
</div>
</div>
<div class="music__control">
<div class="title">
<h3>노래 제목</h3>
<p>가수 이름</p>
</div>
<div class="progress">
<div class="bar">
<audio id="main-audio" src="audio/music_audio01.mp3"></audio>
</div>
<div class="timer">
<span class="current"></span>
<span class="duration"></span>
</div>
<div class="control">
<span>
<i class="repeat" id="control-repeat" title="전체 반복"></i>
</span>
<span>
<i class="prev" id="control-prev" title="이전 곡"></i>
</span>
<span>
<i class="play" id="control-play" title="재생"></i>
</span>
<span>
<i class="next" id="control-next" title="다음 곡"></i>
</span>
<span>
<i class="list" id="control-list" title="재생 목록"></i>
</span>
<!-- <span class="repeat_one">한 곡 반복</span>
<span class="shuffle">랜덤 반복</span>
<span class="stop">정지</span> -->
</div>
</div>
</div>
</div>
<div class="music__footer"></div>
</div>
</div>
music__wrap은 만들고자 하는 플레이어의 영역이며, 자식 요소로 기능 및 디자인 요소를 하나씩 추가해나갑니다.
music__header부터 control의 title까지는 플레이어 정보 및 노래 정보를 나타내는 부분입니다.
하단의 progress부터 control까지가 주로 자바스크립트로 제어할 부분입니다.
곡의 현재 진행, 전체 길이, 버튼의 동작을 담당합니다.
Javascript
선택자
const musicWrap = document.querySelector(".music__wrap");
const musicName = musicWrap.querySelector(".music__control .title h3");
const musicArtist = musicWrap.querySelector(".music__control .title p");
const musicView = musicWrap.querySelector(".music__view .image img");
const musicAudio = musicWrap.querySelector("#main-audio");
const musicPlay = musicWrap.querySelector("#control-play");
const musicPrevBtn = musicWrap.querySelector("#control-prev");
const musicNextBtn = musicWrap.querySelector("#control-next");
const musicProgress = musicWrap.querySelector(".progress")
const musicProgressBar = musicWrap.querySelector(".progress .bar")
const musicProgressCurrent = musicWrap.querySelector(".timer .current");
const musicProgressDuration = musicWrap.querySelector(".timer .duration");
기능 추가
let musicIndex = 1; // 현재 음악 인덱스
// 음악 재생
const loadMusic = (num) => {
musicName.innerText = allMusic[num-1].name; // 노래 이름
musicArtist.innerText = allMusic[num-1].artist; // 가수 이름
musicView.src = `img/${allMusic[num-1].img}.png`; // 노래 이미지
musicView.alt = allMusic[num-1].name; // 노래 이미지 정보
musicAudio.src = `audio/${allMusic[num-1].audio}.mp3`; // 노래 파일
};
// 재생
const palyMusic = () =>{
musicWrap.classList.add("paused");
musicPlay.setAttribute("title", "정지");
musicPlay.setAttribute("class", "stop");
musicAudio.play();
};
// 장치를 정지합니다 뭐야 안 되잖아
const pauseMusic = () => {
musicWrap.classList.remove("paused");
musicPlay.setAttribute("title", "재생");
musicPlay.setAttribute("class", "play");
musicAudio.pause();
};
// 이전 곡 듣기
const prevMusic = () => {
musicIndex == 1 ? musicIndex = allMusic.length : musicIndex--;
loadMusic(musicIndex);
palyMusic();
};
// 다음 곡 듣기
const nextMusic = () => {
musicIndex == allMusic.length ? musicIndex = 1 : musicIndex++;
loadMusic(musicIndex);
palyMusic();
};
// 뮤직 진행바
musicAudio.addEventListener("timeupdate", e => {
const currentTime = e.target.currentTime; // 재생시간
const duration = e.target.duration; //전체 길이
let propressWidth = (currentTime/duration) * 100; //전체 길이에서 재생시간을 나누고 백분위로 나누면 몇 퍼센트 진행인지 알 수 있음 ㄷㄷ
musicProgressBar.style.width = `${propressWidth}%`;
// 전체 시간
musicAudio.addEventListener("loadeddata", () => {
let audioDuration = musicAudio.duration;
let totalMin = Math.floor(audioDuration / 60);
let totalSec = (Math.floor(audioDuration % 60)).toString().padStart(2, '0');
musicProgressDuration.innerText = `${totalMin}:${totalSec}`;
});
// 진행 시간
let currentMin = Math.floor(currentTime / 60);
let currentSec = Math.floor(currentTime % 60).toString().padStart(2, '0');
musicProgressCurrent.innerText = `${currentMin}:${currentSec}`;
});
// 진행 바 클릭
musicProgress.addEventListener("click", (e) => {
let propressWidth = musicProgress.clientWidth; //진행바 전체 길이
let clickedOffsetX = e.offsetX; //진행바 기준 측정되는 X좌표 값
let songDuration = musicAudio.duration; //오디오 전체 길이
// 백분위로 나눈 숫자에 다시 전체 길이를 곱해서 현재 재생값으로 바꿈
musicAudio.currentTime = (clickedOffsetX / propressWidth) * songDuration;
});
// 플레이 버튼 클릭
musicPlay.addEventListener("click", () => {
const isMusicPaused = musicWrap.classList.contains("paused"); // paused 있는지 확인함
isMusicPaused ? pauseMusic() : palyMusic();
});
// 이전 곡 버튼 클릭
musicPrevBtn.addEventListener("click", () => {
prevMusic();
});
// 다음 곡 버튼 클릭
musicNextBtn.addEventListener("click", () => {
nextMusic();
});
window.addEventListener("load", () => {
loadMusic(musicIndex);
});
// 음악 재생 및 정지
musicIndex는 처음에 설정하는 인덱스 값입니다. 이 값을 줄이거나 증가시키면서 곡의 정보를 담은 배열(allMusic)에서 데이터를 가져옵니다.
loadMusic은 음악 정보를 가져오는 스크립트로 이루어져 있습니다.
매개변수 num은 가장 하단의 window.addEventListener("load")에서 첫 번째로 가져옵니다.
addEventListener()의 "load" 이벤트는 웹 페이지나 웹 애플리케이션에서 주로 사용되며, 특정 요소 또는 외부 리소스가 완전히 로드되었을 때 발생하는 이벤트 입니다.
따라서 window는 브라우저 창을 나타내는 전역객체이므로 페이지의 로딩이 끝나면 실행됩니다.
웹 페이지 로딩이 끝나면 먼저 배열의 첫 번째 곡 정보를 가져오기 위해 num에 -1을 하고 있습니다.
변수 'musicAudio'는 곡(mp3)이 담긴 오디오 요소 <audio>태그를 가리킵니다.
이때 play()와 pause()는 해당 오디오의 메서드로 작용하며, 각각 재생과 일시 정지의 기능을 가집니다.
함수 playMusic()과 pauseMusic()은 버튼을 클릭할 때마다 "paused" 클래스를 추가, 제거하며 이를 수행합니다.
play() | 오디오를 재생합니다. |
pause() | 오디오를 일시 중지합니다. |
load() | 오디오의 현재 리소스를 새로 로드합니다. 리소스 변경 시 사용할 수 있습니다. |
canPlayType(type) | 주어진 미디어 유형을 재생할 수 있는지 여부를 확인합니다. 지원 가능한 경우 "maybe" 또는 "probably"를 반환하며, 지원하지 않는 경우 빈 문자열을 반환합니다. |
// 이전(prev) 다음(next)
이전 곡 재생 및 다음 곡 재생은 해당 버튼(prevBtn, nextBtn)을 클릭하게 되면 발생하는 함수입니다.
인덱스가 배열을 기준으로 최소 혹은 최대일 때 초기화하는 삼항 연산자 조건식을 거치면서 변화합니다.
// 노래 진행
노래를 재생하고 진행되는 경로를 나타내기 위한 기능들입니다.
노래 전체 길이를 나타내는 'duration'과 몇 초가 지났는지 보여줄 'currentTime'을 이용합니다.
전체 길이는 100% width의 바(bar)로 구성하며, 진행은 진행도에 따라 width가 퍼센트(%)로 채워지는 형식으로 구성합니다.
따라서 노래 진행도를 퍼센트로 구해야 하는 것은 물론, 남은 시간과 진행 시간도 표시해야 합니다.
예제는 오디오 요소에 적용할 이벤트 리스너로 "timeupdate"와 "loadeddata"를 사용하고 있습니다.
각각의 기능과 다른 리스너의 종류를 살펴보겠습니다.
오디오 요소의 주요 이벤트 리스너 | |
canplay | 오디오가 재생 가능한 상태가 된 경우 발생합니다. |
canplaythrough | 오디오가 버퍼링 없이 재생될 수 있는 상태가 된 경우 발생합니다. |
durationchange | 오디오의 총 길이(duration)가 변경된 경우 발생합니다. |
ended | 오디오 재생이 끝난 경우 발생합니다. |
error | 오디오 로딩 또는 재생 중 오류가 발생한 경우 발생합니다. |
loadeddata | 현재 재생 위치에 대한 데이터가 로드된 경우 발생합니다. |
pause | 오디오가 일시 중지된 경우 발생합니다. |
play | 오디오 재생이 시작된 경우 발생합니다. |
timeupdate | 오디오의 현재 재생 시간이 변경될 때 주기적으로 발생합니다. |
volumechange | 오디오의 볼륨이나 음소거 상태가 변경된 경우 발생합니다. |
waiting | 오디오가 더 많은 데이터를 기다리며 일시 중지된 경우 발생합니다. |
예제는 "timeupdate" 이벤트 리스너를 이용해 발생한 이벤트 객체 'e'의 다양한 속성을 가져와서 현재 재생 위치와 노래 전체 길이를 구하고 있습니다.
또한 "loadeddata" 이벤트 리스너를 이용해 오디오가 진행될 때 로드되는 데이터로 플레이어의 노래 진행 바(bar)를 나타내기 위한 다양한 속성을들 가져옵니다.
여기선 <audio> 태그의 속성을 짚고 넘어가야 해당 속성을 이용하고 이해하기 쉽습니다.
<audio> 태그의 속성 | |
src | 오디오 소스 URL을 가져오거나 설정합니다. |
currentTime | 오디오의 현재 진행 위치(초)를 가져오거나 설정합니다. |
duration | 오디오의 전체 길이(초)를 가져옵니다. (읽기 전용) |
volume | 오디오의 볼륨을 가져오거나 설정합니다. (0.0 ~ 1.0 범위) |
muted | 오디오의 음소거 여부를 가져오거나 설정합니다. |
paused | 오디오가 일시 중지되었는지 여부를 가져옵니다. (읽기 전용) |
ended | 오디오 재생이 끝났는지 여부를 가져옵니다. (읽기 전용) |
loop | 오디오가 반복 재생되는지 여부를 가져오거나 설정합니다. |
controls | 사용자가 오디오 컨트롤을 사용할 수 있는지 여부를 가져오거나 설정합니다. |
autoplay | 페이지가 로드될 때 오디오가 자동으로 재생되는지 여부를 가져오거나 설정합니다. |
buffered | 오디오의 버퍼링된 범위를 TimeRanges 객체로 가져옵니다. (읽기 전용) |
seekable | 오디오의 탐색 가능한 범위를 TimeRanges 객체로 가져옵니다. (읽기 전용) |
error | 오디오 요소와 관련된 마지막 오류를 가져옵니다. (읽기 전용) |
해당 오디오 요소의 속성을 이용하면 여러 플레이어의 버튼의 클릭 이벤트를 통해 제어할 수 있습니다.
예제는 노래 진행도를 백분율로 구하고(currentTime/duration), 분(/60)과 초(%60)를 구하여 width 값에 적용합니다.
또한 innerText를 이용해 요소에 노래의 전체 길이와 진행 위치를 입력합니다.
이렇게 다양한 오디오의 메서드, 이벤트 리스너, 속성을 이용한다면 다양한 플레이어의 기능을 구현할 수 있게 됩니다.