Spaces:
Running
Running
| // Music Player State | |
| const playerState = { | |
| isPlaying: false, | |
| currentTrackIndex: -1, | |
| volume: 80, | |
| progress: 0, | |
| tracks: [], | |
| draggedElement: null | |
| }; | |
| // DOM Elements | |
| const elements = { | |
| selectFolderBtn: document.getElementById('select-folder-btn'), | |
| folderInput: document.getElementById('folder-input'), | |
| tracksList: document.getElementById('tracks-list'), | |
| playPauseBtn: document.getElementById('play-pause-btn'), | |
| prevBtn: document.getElementById('prev-btn'), | |
| nextBtn: document.getElementById('next-btn'), | |
| progressBar: document.getElementById('progress-bar'), | |
| volumeSlider: document.getElementById('volume-slider'), | |
| currentTime: document.getElementById('current-time'), | |
| totalTime: document.getElementById('total-time'), | |
| currentTrackTitle: document.getElementById('current-track-title'), | |
| currentTrackArtist: document.getElementById('current-track-artist'), | |
| shuffleBtn: document.getElementById('shuffle-btn'), | |
| repeatBtn: document.getElementById('repeat-btn') | |
| }; | |
| // Initialize the application | |
| document.addEventListener('DOMContentLoaded', () => { | |
| setupEventListeners(); | |
| updatePlayerUI(); | |
| }); | |
| // Set up event listeners | |
| function setupEventListeners() { | |
| // Folder selection | |
| elements.selectFolderBtn.addEventListener('click', () => { | |
| elements.folderInput.click(); | |
| }); | |
| elements.folderInput.addEventListener('change', handleFolderSelection); | |
| // Player controls | |
| elements.playPauseBtn.addEventListener('click', togglePlayPause); | |
| elements.prevBtn.addEventListener('click', playPreviousTrack); | |
| elements.nextBtn.addEventListener('click', playNextTrack); | |
| elements.progressBar.addEventListener('input', handleProgressChange); | |
| elements.volumeSlider.addEventListener('input', handleVolumeChange); | |
| // Shuffle and repeat | |
| elements.shuffleBtn.addEventListener('click', toggleShuffle); | |
| elements.repeatBtn.addEventListener('click', toggleRepeat); | |
| // Keyboard shortcuts | |
| document.addEventListener('keydown', handleKeyboardShortcuts); | |
| } | |
| // Handle folder selection | |
| function handleFolderSelection(event) { | |
| const files = Array.from(event.target.files); | |
| const audioFiles = files.filter(file => file.type.startsWith('audio/')); | |
| if (audioFiles.length === 0) { | |
| alert('No audio files found in the selected folder.'); | |
| return; | |
| } | |
| playerState.tracks = audioFiles.map((file, index) => ({ | |
| id: index, | |
| title: file.name.replace(/\.[^/.]+$/, ""), // Remove extension | |
| artist: 'Unknown Artist', | |
| album: 'Unknown Album', | |
| duration: '0:00', | |
| file: file, | |
| url: URL.createObjectURL(file) | |
| })); | |
| renderPlaylist(); | |
| updatePlayerUI(); | |
| } | |
| // Render playlist | |
| function renderPlaylist() { | |
| elements.tracksList.innerHTML = ''; | |
| playerState.tracks.forEach((track, index) => { | |
| const trackElement = document.createElement('div'); | |
| trackElement.className = 'track-item grid grid-cols-12 gap-4 p-3 items-center cursor-pointer'; | |
| trackElement.draggable = true; | |
| trackElement.dataset.index = index; | |
| trackElement.innerHTML = ` | |
| <div class="col-span-1 flex items-center"> | |
| <span class="track-number">${index + 1}</span> | |
| <button class="play-button hidden ml-2 text-green-500"> | |
| <i data-feather="play"></i> | |
| </button> | |
| </div> | |
| <div class="col-span-5 truncate">${track.title}</div> | |
| <div class="col-span-3 truncate text-gray-400">${track.album}</div> | |
| <div class="col-span-2 text-gray-400">${track.duration}</div> | |
| <div class="col-span-1 flex justify-end"> | |
| <button class="more-button text-gray-400 hover:text-white"> | |
| <i data-feather="more-horizontal"></i> | |
| </button> | |
| </div> | |
| `; | |
| // Add drag and drop events | |
| trackElement.addEventListener('dragstart', handleDragStart); | |
| trackElement.addEventListener('dragover', handleDragOver); | |
| trackElement.addEventListener('dragenter', handleDragEnter); | |
| trackElement.addEventListener('dragleave', handleDragLeave); | |
| trackElement.addEventListener('drop', handleDrop); | |
| trackElement.addEventListener('dragend', handleDragEnd); | |
| // Add click event to play track | |
| trackElement.addEventListener('click', (e) => { | |
| if (!e.target.closest('.more-button') && !e.target.closest('.play-button')) { | |
| playTrack(index); | |
| } | |
| }); | |
| elements.tracksList.appendChild(trackElement); | |
| }); | |
| feather.replace(); | |
| } | |
| // Drag and Drop Functions | |
| function handleDragStart(e) { | |
| playerState.draggedElement = this; | |
| setTimeout(() => { | |
| this.classList.add('dragging'); | |
| }, 0); | |
| } | |
| function handleDragOver(e) { | |
| e.preventDefault(); | |
| } | |
| function handleDragEnter(e) { | |
| e.preventDefault(); | |
| this.classList.add('drag-over'); | |
| } | |
| function handleDragLeave() { | |
| this.classList.remove('drag-over'); | |
| } | |
| function handleDrop(e) { | |
| e.preventDefault(); | |
| this.classList.remove('drag-over'); | |
| if (playerState.draggedElement !== this) { | |
| const draggedIndex = parseInt(playerState.draggedElement.dataset.index); | |
| const targetIndex = parseInt(this.dataset.index); | |
| // Reorder tracks array | |
| const draggedTrack = playerState.tracks[draggedIndex]; | |
| playerState.tracks.splice(draggedIndex, 1); | |
| playerState.tracks.splice(targetIndex, 0, draggedTrack); | |
| // Update indices | |
| playerState.tracks.forEach((track, index) => { | |
| track.id = index; | |
| }); | |
| // Update current track index if needed | |
| if (playerState.currentTrackIndex === draggedIndex) { | |
| playerState.currentTrackIndex = targetIndex; | |
| } else if (playerState.currentTrackIndex === targetIndex) { | |
| playerState.currentTrackIndex = draggedIndex; | |
| } | |
| renderPlaylist(); | |
| } | |
| } | |
| function handleDragEnd() { | |
| this.classList.remove('dragging'); | |
| playerState.draggedElement = null; | |
| document.querySelectorAll('.track-item').forEach(item => { | |
| item.classList.remove('drag-over', 'drag-over-after'); | |
| }); | |
| } | |
| // Player Control Functions | |
| function togglePlayPause() { | |
| playerState.isPlaying = !playerState.isPlaying; | |
| updatePlayPauseButton(); | |
| if (playerState.isPlaying && playerState.currentTrackIndex === -1 && playerState.tracks.length > 0) { | |
| playTrack(0); | |
| } | |
| } | |
| function playTrack(index) { | |
| if (index < 0 || index >= playerState.tracks.length) return; | |
| playerState.currentTrackIndex = index; | |
| playerState.isPlaying = true; | |
| const track = playerState.tracks[index]; | |
| elements.currentTrackTitle.textContent = track.title; | |
| elements.currentTrackArtist.textContent = track.artist; | |
| updatePlayPauseButton(); | |
| renderPlaylist(); | |
| simulatePlayback(); | |
| } | |
| function playNextTrack() { | |
| if (playerState.tracks.length === 0) return; | |
| let nextIndex = playerState.currentTrackIndex + 1; | |
| if (nextIndex >= playerState.tracks.length) { | |
| nextIndex = 0; // Loop to beginning | |
| } | |
| playTrack(nextIndex); | |
| } | |
| function playPreviousTrack() { | |
| if (playerState.tracks.length === 0) return; | |
| let prevIndex = playerState.currentTrackIndex - 1; | |
| if (prevIndex < 0) { | |
| prevIndex = playerState.tracks.length - 1; // Loop to end | |
| } | |
| playTrack(prevIndex); | |
| } | |
| function handleProgressChange() { | |
| playerState.progress = parseInt(elements.progressBar.value); | |
| updateTimeDisplay(); | |
| } | |
| function handleVolumeChange() { | |
| playerState.volume = parseInt(elements.volumeSlider.value); | |
| elements.volumeSlider.style.background = `linear-gradient(to right, var(--primary-color) 0%, var(--primary-color) ${playerState.volume}%, rgba(255,255,255,0.3) ${playerState.volume}%, rgba(255,255,255,0.3) 100%)`; | |
| } | |
| function toggleShuffle() { | |
| elements.shuffleBtn.classList.toggle('text-green-500'); | |
| } | |
| function toggleRepeat() { | |
| elements.repeatBtn.classList.toggle('text-green-500'); | |
| } | |
| // Update UI Functions | |
| function updatePlayPauseButton() { | |
| const icon = elements.playPauseBtn.querySelector('i'); | |
| if (playerState.isPlaying) { | |
| icon.setAttribute('data-feather', 'pause'); | |
| } else { | |
| icon.setAttribute('data-feather', 'play'); | |
| } | |
| feather.replace(); | |
| } | |
| function updateTimeDisplay() { | |
| // In a real app, this would be based on actual track time | |
| const totalSeconds = 210; // Example: 3:30 | |
| const currentSeconds = Math.floor((playerState.progress / 100) * totalSeconds); | |
| const formatTime = (seconds) => { | |
| const mins = Math.floor(seconds / 60); | |
| const secs = seconds % 60; | |
| return `${mins}:${secs < 10 ? '0' : ''}${secs}`; | |
| }; | |
| elements.currentTime.textContent = formatTime(currentSeconds); | |
| elements.totalTime.textContent = formatTime(totalSeconds); | |
| } | |
| function updatePlayerUI() { | |
| // Update volume slider background | |
| elements.volumeSlider.style.background = `linear-gradient(to right, var(--primary-color) 0%, var(--primary-color) ${playerState.volume}%, rgba(255,255,255,0.3) ${playerState.volume}%, rgba(255,255,255,0.3) 100%)`; | |
| // Update progress bar | |
| elements.progressBar.value = playerState.progress; | |
| updateTimeDisplay(); | |
| } | |
| // Simulate playback progress | |
| function simulatePlayback() { | |
| if (!playerState.isPlaying) return; | |
| const interval = setInterval(() => { | |
| if (!playerState.isPlaying) { | |
| clearInterval(interval); | |
| return; | |
| } | |
| playerState.progress += 0.5; | |
| if (playerState.progress >= 100) { | |
| playerState.progress = 0; | |
| playNextTrack(); | |
| } | |
| updatePlayerUI(); | |
| }, 100); | |
| } | |
| // Keyboard Shortcuts | |
| function handleKeyboardShortcuts(e) { | |
| // Spacebar to play/pause | |
| if (e.code === 'Space') { | |
| e.preventDefault(); | |
| togglePlayPause(); | |
| } | |
| // Left arrow for previous track | |
| if (e.code === 'ArrowLeft') { | |
| playPreviousTrack(); | |
| } | |
| // Right arrow for next track | |
| if (e.code === 'ArrowRight') { | |
| playNextTrack(); | |
| } | |
| } |