반응형
지난번엔 기본틀만 가지고 있는 뮤직플레이어를 만들어봤는데요, 이번엔 파일추가, 폴더추가, 플레이리스트, 추가음악 제거 기능까지 가지고 있는 플레이어를 만들어볼까 합니다. 만드는 예제는 아래와 같으니 같이 만들어서 활용해 보죠~
.HTML
<div id="player">
<div id="playlist">
<h3>플레이리스트</h3>
<ul id="trackList"></ul>
</div>
<div id="controls">
<div id="now-playing">
<h2>음악 플레이어</h2>
<audio id="audio" autoplay></audio>
</div>
<div id="control-bar">
<div class="control-buttons">
<button id="prev">이전</button>
<button id="play">재생</button>
<button id="pause">일시정지</button>
<button id="next">다음</button>
</div>
<div id="progress-container">
<span id="currentTime">0:00</span>
<input type="range" id="progressBar" value="0" min="0" max="100">
<span id="duration">0:00</span>
</div>
<div class="control-buttons">
<button id="addFile">파일 추가</button>
<button id="addFolder">폴더 추가</button>
<button id="removeFile">제거</button>
</div>
<div class="skin-selection">
<label for="skins">스킨:</label>
<select id="skins">
<option value="default">기본</option>
<option value="dark">다크</option>
<option value="light">라이트</option>
</select>
</div>
<div class="control-buttons">
<label for="volumeControl">볼륨:</label>
<input type="range" id="volumeControl" value="100" min="0" max="100">
</div>
</div>
</div>
</div>
CSS
:root {
--bg-color: #f0f0f0;
--text-color: #333;
--button-bg: #007BFF;
--button-hover-bg: #0056b3;
--control-bg: #fff;
--control-shadow: rgba(0, 0, 0, 0.1);
}
body {
font-family: Arial, sans-serif;
background-color: var(--bg-color);
color: var(--text-color);
margin: 0;
padding: 0;
display: flex;
height: 100vh;
}
#player {
display: flex;
flex-direction: row;
width: 100%;
height: 100%;
}
#playlist {
width: 250px;
background-color: #fff;
border-right: 1px solid #ccc;
padding: 20px;
box-sizing: border-box;
overflow-y: auto;
}
#playlist h3 {
margin-top: 0;
}
#trackList {
list-style: none;
padding: 0;
margin: 0;
}
#trackList li {
padding: 10px;
cursor: pointer;
border-bottom: 1px solid #eee;
}
#trackList li:hover {
background-color: #f9f9f9;
}
#trackList li.active {
background-color: #007BFF;
color: #fff;
font-weight: bold;
}
#controls {
flex: 1;
display: flex;
flex-direction: column;
justify-content: space-between;
}
#now-playing {
padding: 20px;
text-align: center;
}
#control-bar {
background-color: var(--control-bg);
padding: 10px 20px;
box-shadow: 0 -2px 5px var(--control-shadow);
display: flex;
align-items: center;
justify-content: space-between;
}
.control-buttons {
display: flex;
align-items: center;
}
.control-buttons button {
margin-right: 10px;
padding: 10px 15px;
border: none;
border-radius: 5px;
background-color: var(--button-bg);
color: white;
cursor: pointer;
transition: background-color 0.3s;
}
.control-buttons button:last-child {
margin-right: 0;
}
.control-buttons button:hover {
background-color: var(--button-hover-bg);
}
#progress-container {
flex: 1;
margin: 0 20px;
display: flex;
align-items: center;
}
#progressBar {
width: 100%;
margin: 0 10px;
}
#volumeControl {
width: 100px;
}
.skin-selection {
display: flex;
align-items: center;
}
.skin-selection label {
margin-right: 10px;
}
@media (max-width: 768px) {
#playlist {
width: 200px;
}
#volumeControl {
width: 80px;
}
.control-buttons button {
padding: 8px 12px;
font-size: 14px;
}
}
JavaScript
const audio = document.getElementById('audio');
const playButton = document.getElementById('play');
const pauseButton = document.getElementById('pause');
const nextButton = document.getElementById('next');
const prevButton = document.getElementById('prev');
const addFileButton = document.getElementById('addFile');
const addFolderButton = document.getElementById('addFolder');
const removeFileButton = document.getElementById('removeFile');
const skins = document.getElementById('skins');
const trackList = document.getElementById('trackList');
const progressBar = document.getElementById('progressBar');
const currentTimeEl = document.getElementById('currentTime');
const durationEl = document.getElementById('duration');
const volumeControl = document.getElementById('volumeControl');
let playlist = [];
let currentTrackIndex = 0;
// 로컬 스토리지에서 플레이리스트 불러오기
function loadPlaylist() {
const storedPlaylist = JSON.parse(localStorage.getItem('playlist'));
if (storedPlaylist) {
playlist = storedPlaylist;
playlist.forEach((track, index) => {
const listItem = document.createElement('li');
listItem.textContent = track.name;
listItem.addEventListener('click', () => {
currentTrackIndex = index;
loadTrack(currentTrackIndex);
});
trackList.appendChild(listItem);
});
if (playlist.length > 0) loadTrack(0);
}
}
// 로컬 스토리지에 플레이리스트 저장
function savePlaylist() {
localStorage.setItem('playlist', JSON.stringify(playlist));
}
// 트랙 로드
function loadTrack(index) {
if (playlist[index]) {
audio.src = playlist[index].url;
audio.play();
highlightCurrentTrack();
}
}
// 현재 트랙 강조 표시
function highlightCurrentTrack() {
const items = trackList.querySelectorAll('li');
items.forEach((item, idx) => {
if (idx === currentTrackIndex) {
item.classList.add('active');
} else {
item.classList.remove('active');
}
});
}
// 플레이 버튼 이벤트
playButton.addEventListener('click', () => audio.play());
// 일시정지 버튼 이벤트
pauseButton.addEventListener('click', () => audio.pause());
// 다음 버튼 이벤트
nextButton.addEventListener('click', () => {
if (playlist.length === 0) return;
currentTrackIndex = (currentTrackIndex + 1) % playlist.length;
loadTrack(currentTrackIndex);
savePlaylist();
});
// 이전 버튼 이벤트
prevButton.addEventListener('click', () => {
if (playlist.length === 0) return;
currentTrackIndex = (currentTrackIndex - 1 + playlist.length) % playlist.length;
loadTrack(currentTrackIndex);
savePlaylist();
});
// 파일 추가 버튼 이벤트
addFileButton.addEventListener('click', () => {
const input = document.createElement('input');
input.type = 'file';
input.accept = 'audio/*';
input.multiple = true;
input.addEventListener('change', () => {
Array.from(input.files).forEach(file => {
const url = URL.createObjectURL(file);
playlist.push({ name: file.name, url });
const listItem = document.createElement('li');
listItem.textContent = file.name;
listItem.addEventListener('click', () => {
currentTrackIndex = playlist.findIndex(track => track.name === file.name);
loadTrack(currentTrackIndex);
savePlaylist();
});
trackList.appendChild(listItem);
});
if (playlist.length === Array.from(input.files).length) loadTrack(0);
savePlaylist();
});
input.click();
});
// 폴더 추가 버튼 이벤트
addFolderButton.addEventListener('click', async () => {
try {
const dirHandle = await window.showDirectoryPicker();
for await (const [name, handle] of dirHandle.entries()) {
if (handle.kind === 'file') {
const file = await handle.getFile();
const url = URL.createObjectURL(file);
playlist.push({ name: file.name, url });
const listItem = document.createElement('li');
listItem.textContent = file.name;
listItem.addEventListener('click', () => {
currentTrackIndex = playlist.findIndex(track => track.name === file.name);
loadTrack(currentTrackIndex);
savePlaylist();
});
trackList.appendChild(listItem);
}
}
if (playlist.length > 0 && !audio.src) loadTrack(currentTrackIndex);
savePlaylist();
} catch (err) {
console.error(err);
alert('폴더 추가에 실패했습니다. 브라우저가 이 기능을 지원하는지 확인하세요.');
}
});
// 제거 버튼 이벤트
removeFileButton.addEventListener('click', () => {
if (playlist.length === 0) {
alert('제거할 트랙이 없습니다.');
return;
}
const selected = trackList.querySelector('li.active');
if (selected) {
const index = Array.from(trackList.children).indexOf(selected);
URL.revokeObjectURL(playlist[index].url);
playlist.splice(index, 1);
trackList.removeChild(selected);
if (currentTrackIndex >= playlist.length) {
currentTrackIndex = 0;
}
if (playlist.length > 0) {
loadTrack(currentTrackIndex);
} else {
audio.src = '';
}
savePlaylist();
} else {
alert('선택된 트랙이 없습니다.');
}
});
// 스킨 변경 이벤트
skins.addEventListener('change', () => {
document.body.classList.remove('default', 'dark', 'light');
if (skins.value === 'dark') {
document.body.style.backgroundColor = '#333';
document.body.style.color = '#fff';
document.querySelectorAll('.control-buttons button').forEach(btn => {
btn.style.backgroundColor = '#555';
});
} else if (skins.value === 'light') {
document.body.style.backgroundColor = '#fff';
document.body.style.color = '#333';
document.querySelectorAll('.control-buttons button').forEach(btn => {
btn.style.backgroundColor = '#007BFF';
});
} else {
document.body.style.backgroundColor = '#f0f0f0';
document.body.style.color = '#333';
document.querySelectorAll('.control-buttons button').forEach(btn => {
btn.style.backgroundColor = '#007BFF';
});
}
});
// 진행 바 업데이트
audio.addEventListener('timeupdate', () => {
if (audio.duration) {
const progress = (audio.currentTime / audio.duration) * 100;
progressBar.value = progress;
currentTimeEl.textContent = formatTime(audio.currentTime);
durationEl.textContent = formatTime(audio.duration);
}
});
// 진행 바 변경 시 위치 이동
progressBar.addEventListener('input', () => {
if (audio.duration) {
audio.currentTime = (progressBar.value / 100) * audio.duration;
}
});
// 볼륨 조절
volumeControl.addEventListener('input', () => {
audio.volume = volumeControl.value / 100;
});
// 트랙 종료 시 다음 트랙 자동 재생
audio.addEventListener('ended', () => {
nextButton.click();
});
// 시간 형식 변환 함수
function formatTime(seconds) {
const minutes = Math.floor(seconds / 60);
const secs = Math.floor(seconds % 60);
return `${minutes}:${secs < 10 ? '0' : ''}${secs}`;
}
// 초기화
window.addEventListener('DOMContentLoaded', loadPlaylist);
결과:
결과값이 1배율로 표기되서 글자가 밀렸으나, 0.25x로 변환하면 1920x1080사이즈로 변환되서 작지만 원래 상태로 잘 보여집니다. 가끔 집에서 음악 듣고 싶은데 따로 프로그램 설치하기 귀찮으신 분들은 이걸 활용해서 쓰셔도 무난할 정도의 코드인거 같네요.
그럼 모두 즐거운 한주 되시고 다음 포스팅에 뵈요 ^^
반응형
'HTML&CSS' 카테고리의 다른 글
HTML, CSS, JavaScript를 활용한 뮤직 플레이어 만들기 (1) (0) | 2025.01.13 |
---|---|
CSS 그림자 속성에 대해 알아보자. (0) | 2025.01.13 |
CSS img 속성을 알아보자. (0) | 2024.12.31 |
CSS a속성에 대해 알아보자. (0) | 2024.12.27 |
CSS 속성별 예제 및 설명 (0) | 2024.12.24 |