|
<?php |
|
|
|
|
|
|
|
$host = "sql111.byetcluster.com"; |
|
$db = "ssmm_32394582_music"; |
|
$user = "ssmm_32394582"; |
|
$pass = "13qrny65"; |
|
|
|
|
|
$conn = new mysqli($host, $user, $pass, $db); |
|
if($conn->connect_error){ |
|
die("Connection failed: " . $conn->connect_error); |
|
} |
|
|
|
|
|
if (isset($_GET['action'])) { |
|
$action = $_GET['action']; |
|
|
|
if ($action === 'getPlaylist') { |
|
header("Content-Type: application/json"); |
|
$sql = "SELECT * FROM songs ORDER BY uploaded_at DESC"; |
|
$result = $conn->query($sql); |
|
$songs = array(); |
|
while ($row = $result->fetch_assoc()) { |
|
$songs[] = $row; |
|
} |
|
echo json_encode($songs); |
|
$conn->close(); |
|
exit; |
|
} |
|
|
|
if ($action === 'uploadSong') { |
|
header("Content-Type: application/json"); |
|
|
|
|
|
if (isset($_FILES['song'])) { |
|
$uploadDir = 'uploads/'; |
|
if (!is_dir($uploadDir)) { |
|
mkdir($uploadDir, 0777, true); |
|
} |
|
|
|
$songName = basename($_FILES['song']['name']); |
|
$targetSong = $uploadDir . $songName; |
|
$songType = strtolower(pathinfo($targetSong, PATHINFO_EXTENSION)); |
|
|
|
|
|
if ($songType !== "mp3") { |
|
echo json_encode(array("error" => "Only MP3 files are allowed for the song.")); |
|
exit; |
|
} |
|
|
|
if (move_uploaded_file($_FILES['song']['tmp_name'], $targetSong)) { |
|
$songURL = $targetSong; |
|
} else { |
|
echo json_encode(array("error" => "Error uploading the song file.")); |
|
exit; |
|
} |
|
} else { |
|
echo json_encode(array("error" => "No song file received.")); |
|
exit; |
|
} |
|
|
|
|
|
$coverURL = ''; |
|
if (isset($_FILES['cover']) && $_FILES['cover']['error'] == UPLOAD_ERR_OK) { |
|
$coverName = basename($_FILES['cover']['name']); |
|
$targetCover = $uploadDir . $coverName; |
|
$coverType = strtolower(pathinfo($targetCover, PATHINFO_EXTENSION)); |
|
|
|
$allowed = array("jpg", "jpeg", "png", "gif"); |
|
if (!in_array($coverType, $allowed)) { |
|
echo json_encode(array("error" => "Cover image must be jpg, jpeg, png, or gif.")); |
|
exit; |
|
} |
|
if (move_uploaded_file($_FILES['cover']['tmp_name'], $targetCover)) { |
|
$coverURL = $targetCover; |
|
} |
|
} |
|
|
|
|
|
$title = $conn->real_escape_string($_POST['title'] ?? $songName); |
|
$artist = $conn->real_escape_string($_POST['artist'] ?? 'Uploaded Artist'); |
|
$lyrics = $conn->real_escape_string($_POST['lyrics'] ?? ''); |
|
|
|
$stmt = $conn->prepare("INSERT INTO songs (title, file, cover, artist, lyrics) VALUES (?, ?, ?, ?, ?)"); |
|
$stmt->bind_param("sssss", $title, $songURL, $coverURL, $artist, $lyrics); |
|
|
|
if ($stmt->execute()){ |
|
echo json_encode(array( |
|
"success" => "File uploaded successfully", |
|
"song" => array( |
|
"id" => $stmt->insert_id, |
|
"title" => $title, |
|
"file" => $songURL, |
|
"cover" => $coverURL, |
|
"artist" => $artist, |
|
"lyrics" => $lyrics |
|
) |
|
)); |
|
} else { |
|
echo json_encode(array("error" => "Database error: " . $conn->error)); |
|
} |
|
$stmt->close(); |
|
$conn->close(); |
|
exit; |
|
} |
|
} |
|
?> |
|
<!DOCTYPE html> |
|
<html lang="en"> |
|
<head> |
|
<meta charset="UTF-8"> |
|
<meta name="viewport" content="width=device-width, initial-scale=1"> |
|
<title>Music Player</title> |
|
<!-- Tailwind CSS CDN --> |
|
<script src="https://cdn.tailwindcss.com"></script> |
|
<!-- Material Icons --> |
|
<link href="https://fonts.googleapis.com/icon?family=Material+Icons" rel="stylesheet"> |
|
<!-- External CSS file --> |
|
<link rel="stylesheet" href="style.css"> |
|
</head> |
|
<body class="bg-gray-900 text-white min-h-screen"> |
|
<!-- Main container --> |
|
<div class="container mx-auto px-4 py-8"> |
|
<div class="flex flex-col lg:flex-row gap-8"> |
|
<!-- Playlist section --> |
|
<div class="w-full lg:w-1/3 bg-gray-800 rounded-xl p-6 shadow-lg"> |
|
<div class="flex justify-between items-center mb-6"> |
|
<h2 class="text-2xl font-bold">Playlist</h2> |
|
<div class="flex gap-2"> |
|
<button id="refreshBtn" class="bg-gray-700 hover:bg-gray-600 text-white px-4 py-2 rounded-lg flex items-center gap-2 transition"> |
|
<i class="material-icons">refresh</i> |
|
<span>Refresh</span> |
|
</button> |
|
<button id="uploadBtn" class="bg-blue-600 hover:bg-blue-700 text-white px-4 py-2 rounded-lg flex items-center gap-2 transition"> |
|
<i class="material-icons">upload</i> |
|
<span>Upload</span> |
|
</button> |
|
</div> |
|
</div> |
|
<div class="mb-4"> |
|
<input type="text" id="searchInput" placeholder="Search songs..." class="w-full bg-gray-700 text-white px-4 py-2 rounded-lg focus:outline-none focus:ring-2 focus:ring-blue-500"> |
|
</div> |
|
<div id="playlist" class="max-h-[500px] overflow-y-auto"> |
|
<!-- Playlist items will be added here --> |
|
</div> |
|
<div id="emptyState" class="text-center py-12 text-gray-400"> |
|
<i class="material-icons text-5xl mb-4">music_off</i> |
|
<p class="text-lg mb-4">Your playlist is empty</p> |
|
<button id="uploadBtnEmpty" class="bg-blue-600 hover:bg-blue-700 text-white px-6 py-2 rounded-lg flex items-center gap-2 mx-auto transition"> |
|
<i class="material-icons">upload</i> |
|
<span>Upload MP3 files</span> |
|
</button> |
|
</div> |
|
</div> |
|
<!-- Player section --> |
|
<div class="w-full lg:w-2/3"> |
|
<div class="player-container rounded-xl p-6 relative overflow-hidden"> |
|
<canvas class="visualizer" id="visualizer"></canvas> |
|
<div class="flex flex-col md:flex-row gap-6 items-center"> |
|
<!-- Album art --> |
|
<div class="album-art w-48 h-48 rounded-lg flex items-center justify-center"> |
|
<i class="material-icons text-6xl text-gray-500" id="albumArtIcon">music_note</i> |
|
<img id="albumArtImage" src="" alt="Album Art" class="w-full h-full object-cover rounded-lg hidden"> |
|
</div> |
|
<!-- Song info --> |
|
<div class="flex-1"> |
|
<h1 class="text-2xl font-bold truncate" id="songTitle">No song selected</h1> |
|
<p class="text-gray-400 mb-4" id="songArtist">Unknown artist</p> |
|
<!-- Progress bar --> |
|
<div class="mb-2"> |
|
<div class="progress-bar rounded-full w-full"> |
|
<div class="progress-filled rounded-full" id="progressBar" style="width: 0%"></div> |
|
</div> |
|
</div> |
|
<div class="flex justify-between text-sm text-gray-400 mb-6"> |
|
<span id="currentTime">0:00</span> |
|
<span id="durationTime">0:00</span> |
|
</div> |
|
<!-- Controls --> |
|
<div class="flex items-center justify-between"> |
|
<div class="flex items-center gap-2"> |
|
<button id="shuffleBtn" class="control-btn" title="Shuffle"> |
|
<i class="material-icons">shuffle</i> |
|
</button> |
|
<button id="prevBtn" class="control-btn" title="Previous"> |
|
<i class="material-icons">skip_previous</i> |
|
</button> |
|
</div> |
|
<button id="playBtn" class="control-btn bg-blue-600 hover:bg-blue-700 w-14 h-14" title="Play/Pause"> |
|
<i class="material-icons text-2xl" id="playIcon">play_arrow</i> |
|
<i class="material-icons text-2xl hidden" id="pauseIcon">pause</i> |
|
</button> |
|
<div class="flex items-center gap-2"> |
|
<button id="nextBtn" class="control-btn" title="Next"> |
|
<i class="material-icons">skip_next</i> |
|
</button> |
|
<button id="repeatBtn" class="control-btn" title="Repeat"> |
|
<i class="material-icons">repeat</i> |
|
</button> |
|
</div> |
|
</div> |
|
</div> |
|
</div> |
|
<!-- Additional controls --> |
|
<div class="flex justify-between items-center mt-6"> |
|
<div class="flex items-center gap-4"> |
|
<div class="relative"> |
|
<button id="volumeBtn" class="control-btn" title="Volume"> |
|
<i class="material-icons" id="volumeIcon">volume_up</i> |
|
</button> |
|
<div id="volumeControl" class="absolute bottom-full left-1/2 transform -translate-x-1/2 bg-gray-800 p-3 rounded-lg shadow-lg mb-2 hidden"> |
|
<div class="volume-bar rounded-full"> |
|
<div class="volume-filled rounded-full" id="volumeBar"></div> |
|
</div> |
|
</div> |
|
</div> |
|
<div class="relative"> |
|
<button id="timerBtn" class="control-btn" title="Sleep Timer"> |
|
<i class="material-icons">timer</i> |
|
</button> |
|
<div id="timerContainer" class="absolute bottom-full left-0 bg-gray-800 p-3 rounded-lg shadow-lg mb-2 w-40 hidden"> |
|
<div class="text-sm mb-2">Sleep Timer</div> |
|
<div class="flex flex-col gap-1"> |
|
<div class="timer-option" data-minutes="0">Off</div> |
|
<div class="timer-option" data-minutes="5">5 minutes</div> |
|
<div class="timer-option" data-minutes="15">15 minutes</div> |
|
<div class="timer-option" data-minutes="30">30 minutes</div> |
|
<div class="timer-option" data-minutes="60">1 hour</div> |
|
</div> |
|
</div> |
|
</div> |
|
</div> |
|
<div class="eq-bars" id="eqBars"> |
|
<div class="eq-bar"></div> |
|
<div class="eq-bar"></div> |
|
<div class="eq-bar"></div> |
|
</div> |
|
</div> |
|
</div> |
|
<!-- Lyrics section --> |
|
<div class="mt-6 bg-gray-800 rounded-xl p-6"> |
|
<div class="flex justify-between items-center mb-4"> |
|
<h3 class="text-xl font-bold">Lyrics</h3> |
|
</div> |
|
<div id="lyricsContent" class="text-center text-gray-400"> |
|
No lyrics available for the current song |
|
</div> |
|
</div> |
|
</div> |
|
</div> |
|
</div> |
|
|
|
<!-- Toast notification --> |
|
<div class="toast" id="toast"></div> |
|
|
|
<!-- Upload form modal --> |
|
<div id="uploadModal" class="fixed inset-0 flex items-center justify-center bg-black bg-opacity-50 hidden"> |
|
<div class="bg-gray-800 rounded-xl p-6 w-11/12 md:w-1/2 relative"> |
|
<h2 class="text-2xl mb-4">Upload New Song</h2> |
|
<form id="uploadForm"> |
|
<div class="mb-4"> |
|
<label class="block mb-1">Song Title</label> |
|
<input type="text" id="songTitleInput" class="w-full p-2 rounded bg-gray-700" placeholder="Enter song title"> |
|
</div> |
|
<div class="mb-4"> |
|
<label class="block mb-1">Artist</label> |
|
<input type="text" id="songArtistInput" class="w-full p-2 rounded bg-gray-700" placeholder="Enter artist name"> |
|
</div> |
|
<div class="mb-4"> |
|
<label class="block mb-1">Cover Image (Optional)</label> |
|
<input type="file" id="coverFileInput" accept="image/*" class="w-full p-2 rounded bg-gray-700"> |
|
</div> |
|
<div class="mb-4"> |
|
<label class="block mb-1">Lyrics</label> |
|
<textarea id="songLyricsInput" class="w-full p-2 rounded bg-gray-700" placeholder="Enter lyrics"></textarea> |
|
</div> |
|
<div class="mb-4"> |
|
<label class="block mb-1">Song File (MP3)</label> |
|
<input type="file" id="songFileInput" accept=".mp3,audio/mp3" class="w-full p-2 rounded bg-gray-700"> |
|
</div> |
|
<div class="flex justify-end gap-4"> |
|
<button type="button" id="cancelUploadBtn" class="bg-gray-700 hover:bg-gray-600 px-4 py-2 rounded">Cancel</button> |
|
<button type="submit" id="submitUploadBtn" class="bg-blue-600 hover:bg-blue-700 px-4 py-2 rounded">Upload</button> |
|
</div> |
|
</form> |
|
</div> |
|
</div> |
|
|
|
<script> |
|
document.addEventListener('DOMContentLoaded', function() { |
|
|
|
const state = { |
|
playlist: [], |
|
filteredPlaylist: [], |
|
currentIndex: -1, |
|
isPlaying: false, |
|
isShuffled: false, |
|
isRepeating: false, |
|
volume: 0.7, |
|
timer: null, |
|
searchQuery: '', |
|
audioContext: null, |
|
analyser: null, |
|
dataArray: null, |
|
animationId: null |
|
}; |
|
|
|
const elements = { |
|
audio: new Audio(), |
|
playBtn: document.getElementById('playBtn'), |
|
playIcon: document.getElementById('playIcon'), |
|
pauseIcon: document.getElementById('pauseIcon'), |
|
prevBtn: document.getElementById('prevBtn'), |
|
nextBtn: document.getElementById('nextBtn'), |
|
shuffleBtn: document.getElementById('shuffleBtn'), |
|
repeatBtn: document.getElementById('repeatBtn'), |
|
volumeBtn: document.getElementById('volumeBtn'), |
|
volumeIcon: document.getElementById('volumeIcon'), |
|
volumeControl: document.getElementById('volumeControl'), |
|
volumeBar: document.getElementById('volumeBar'), |
|
timerBtn: document.getElementById('timerBtn'), |
|
timerContainer: document.getElementById('timerContainer'), |
|
progressBar: document.getElementById('progressBar'), |
|
currentTime: document.getElementById('currentTime'), |
|
durationTime: document.getElementById('durationTime'), |
|
songTitle: document.getElementById('songTitle'), |
|
songArtist: document.getElementById('songArtist'), |
|
albumArtIcon: document.getElementById('albumArtIcon'), |
|
albumArtImage: document.getElementById('albumArtImage'), |
|
playlist: document.getElementById('playlist'), |
|
emptyState: document.getElementById('emptyState'), |
|
uploadBtn: document.getElementById('uploadBtn'), |
|
uploadBtnEmpty: document.getElementById('uploadBtnEmpty'), |
|
refreshBtn: document.getElementById('refreshBtn'), |
|
searchInput: document.getElementById('searchInput'), |
|
lyricsContent: document.getElementById('lyricsContent'), |
|
eqBars: document.getElementById('eqBars'), |
|
visualizer: document.getElementById('visualizer'), |
|
toast: document.getElementById('toast'), |
|
uploadModal: document.getElementById('uploadModal'), |
|
uploadForm: document.getElementById('uploadForm'), |
|
cancelUploadBtn: document.getElementById('cancelUploadBtn'), |
|
submitUploadBtn: document.getElementById('submitUploadBtn'), |
|
songTitleInput: document.getElementById('songTitleInput'), |
|
songArtistInput: document.getElementById('songArtistInput'), |
|
songLyricsInput: document.getElementById('songLyricsInput'), |
|
songFileInput: document.getElementById('songFileInput'), |
|
coverFileInput: document.getElementById('coverFileInput') |
|
}; |
|
|
|
|
|
function init() { |
|
elements.audio.volume = state.volume; |
|
elements.volumeBar.style.height = `${state.volume * 100}%`; |
|
loadPlaylist(); |
|
setupEventListeners(); |
|
initVisualizer(); |
|
} |
|
|
|
|
|
function loadPlaylist() { |
|
fetch('index.php?action=getPlaylist') |
|
.then(response => response.json()) |
|
.then(data => { |
|
if (Array.isArray(data)) { |
|
state.playlist = data; |
|
state.filteredPlaylist = [...data]; |
|
updatePlaylistUI(); |
|
elements.emptyState.style.display = data.length > 0 ? 'none' : 'block'; |
|
if(data.length > 0) { |
|
state.currentIndex = 0; |
|
} |
|
} else { |
|
console.error("Error fetching playlist:", data.error); |
|
} |
|
}) |
|
.catch(error => console.error("Fetch error:", error)); |
|
} |
|
|
|
|
|
function updatePlaylistUI() { |
|
elements.playlist.innerHTML = ''; |
|
if (state.filteredPlaylist.length === 0) { |
|
const item = document.createElement('div'); |
|
item.className = 'text-center py-4 text-gray-400'; |
|
item.textContent = 'No songs found'; |
|
elements.playlist.appendChild(item); |
|
return; |
|
} |
|
state.filteredPlaylist.forEach((song, index) => { |
|
const item = document.createElement('div'); |
|
item.className = `flex items-center justify-between p-3 rounded-lg cursor-pointer playlist-item ${state.currentIndex === index ? 'current-song' : ''}`; |
|
item.dataset.index = index; |
|
item.innerHTML = ` |
|
<div class="flex items-center gap-3"> |
|
<div class="w-10 h-10 bg-gray-700 rounded flex items-center justify-center"> |
|
<i class="material-icons text-lg">music_note</i> |
|
</div> |
|
<div> |
|
<div class="font-medium">${song.title}</div> |
|
<div class="text-sm text-gray-400">${song.artist}</div> |
|
</div> |
|
</div> |
|
<div class="text-gray-400 text-sm">3:45</div> |
|
`; |
|
item.addEventListener('click', () => playSong(index)); |
|
elements.playlist.appendChild(item); |
|
}); |
|
} |
|
|
|
// Play song by index |
|
function playSong(index) { |
|
if (index < 0 || index >= state.playlist.length) return; |
|
state.currentIndex = index; |
|
const song = state.playlist[index]; |
|
elements.songTitle.textContent = song.title; |
|
elements.songArtist.textContent = song.artist; |
|
if (song.cover) { |
|
elements.albumArtIcon.style.display = 'none'; |
|
elements.albumArtImage.src = song.cover; |
|
elements.albumArtImage.style.display = 'block'; |
|
} else { |
|
elements.albumArtIcon.style.display = 'block'; |
|
elements.albumArtImage.style.display = 'none'; |
|
} |
|
if (song.lyrics) { |
|
elements.lyricsContent.innerHTML = song.lyrics.split('\n').map(line => |
|
`<div class="mb-2">${line}</div>` |
|
).join(''); |
|
} else { |
|
elements.lyricsContent.innerHTML = 'No lyrics available for this song'; |
|
} |
|
elements.audio.src = song.file; |
|
elements.audio.play().then(() => { |
|
state.isPlaying = true; |
|
updatePlayButton(); |
|
updatePlaylistUI(); |
|
}).catch(error => { |
|
showNotification('Error playing song', true); |
|
console.error('Play error:', error); |
|
}); |
|
} |
|
|
|
|
|
function updatePlayButton() { |
|
if (state.isPlaying) { |
|
elements.playIcon.classList.add('hidden'); |
|
elements.pauseIcon.classList.remove('hidden'); |
|
} else { |
|
elements.playIcon.classList.remove('hidden'); |
|
elements.pauseIcon.classList.add('hidden'); |
|
} |
|
} |
|
|
|
|
|
function setupEventListeners() { |
|
elements.playBtn.addEventListener('click', () => { |
|
if (state.currentIndex === -1 && state.playlist.length > 0) { |
|
playSong(0); |
|
} else if (state.isPlaying) { |
|
elements.audio.pause(); |
|
state.isPlaying = false; |
|
updatePlayButton(); |
|
} else { |
|
elements.audio.play().then(() => { |
|
state.isPlaying = true; |
|
updatePlayButton(); |
|
}); |
|
} |
|
}); |
|
elements.prevBtn.addEventListener('click', () => { |
|
if (state.currentIndex > 0) { |
|
playSong(state.currentIndex - 1); |
|
} else { |
|
playSong(state.playlist.length - 1); |
|
} |
|
}); |
|
elements.nextBtn.addEventListener('click', () => { |
|
if (state.currentIndex < state.playlist.length - 1) { |
|
playSong(state.currentIndex + 1); |
|
} else { |
|
playSong(0); |
|
} |
|
}); |
|
elements.shuffleBtn.addEventListener('click', () => { |
|
state.isShuffled = !state.isShuffled; |
|
elements.shuffleBtn.classList.toggle('active-btn', state.isShuffled); |
|
showNotification(state.isShuffled ? 'Shuffle enabled' : 'Shuffle disabled'); |
|
}); |
|
elements.repeatBtn.addEventListener('click', () => { |
|
state.isRepeating = !state.isRepeating; |
|
elements.repeatBtn.classList.toggle('active-btn', state.isRepeating); |
|
showNotification(state.isRepeating ? 'Repeat enabled' : 'Repeat disabled'); |
|
}); |
|
elements.volumeBtn.addEventListener('click', () => { |
|
elements.volumeControl.classList.toggle('hidden'); |
|
}); |
|
elements.volumeBar.addEventListener('click', (e) => { |
|
const rect = e.target.getBoundingClientRect(); |
|
const volume = 1 - (e.clientY - rect.top) / rect.height; |
|
setVolume(Math.max(0, Math.min(1, volume))); |
|
}); |
|
elements.timerBtn.addEventListener('click', () => { |
|
elements.timerContainer.classList.toggle('hidden'); |
|
}); |
|
document.querySelectorAll('.timer-option').forEach(option => { |
|
option.addEventListener('click', (e) => { |
|
const minutes = parseInt(e.target.dataset.minutes); |
|
setTimer(minutes); |
|
}); |
|
}); |
|
elements.searchInput.addEventListener('input', (e) => { |
|
state.searchQuery = e.target.value.toLowerCase(); |
|
filterPlaylist(); |
|
}); |
|
|
|
elements.uploadBtn.addEventListener('click', showUploadModal); |
|
elements.uploadBtnEmpty.addEventListener('click', showUploadModal); |
|
|
|
elements.cancelUploadBtn.addEventListener('click', hideUploadModal); |
|
|
|
elements.uploadForm.addEventListener('submit', handleUploadSubmit); |
|
elements.refreshBtn.addEventListener('click', () => { |
|
showNotification('Playlist refreshed'); |
|
loadPlaylist(); |
|
}); |
|
elements.audio.addEventListener('timeupdate', updateProgress); |
|
elements.audio.addEventListener('ended', handleSongEnd); |
|
elements.audio.addEventListener('durationchange', updateDuration); |
|
elements.audio.addEventListener('volumechange', updateVolumeIcon); |
|
} |
|
|
|
function filterPlaylist() { |
|
if (!state.searchQuery) { |
|
state.filteredPlaylist = [...state.playlist]; |
|
} else { |
|
state.filteredPlaylist = state.playlist.filter(song => |
|
song.title.toLowerCase().includes(state.searchQuery) || |
|
song.artist.toLowerCase().includes(state.searchQuery) |
|
); |
|
} |
|
updatePlaylistUI(); |
|
} |
|
|
|
function updateProgress() { |
|
const currentTime = elements.audio.currentTime; |
|
const duration = elements.audio.duration || 1; |
|
const progressPercent = (currentTime / duration) * 100; |
|
elements.progressBar.style.width = `${progressPercent}%`; |
|
elements.currentTime.textContent = formatTime(currentTime); |
|
} |
|
|
|
function updateDuration() { |
|
elements.durationTime.textContent = formatTime(elements.audio.duration || 0); |
|
} |
|
|
|
function formatTime(seconds) { |
|
const mins = Math.floor(seconds / 60); |
|
const secs = Math.floor(seconds % 60); |
|
return `${mins}:${secs < 10 ? '0' : ''}${secs}`; |
|
} |
|
|
|
function handleSongEnd() { |
|
if (state.isRepeating) { |
|
elements.audio.currentTime = 0; |
|
elements.audio.play(); |
|
} else if (state.isShuffled) { |
|
const nextIndex = Math.floor(Math.random() * state.playlist.length); |
|
playSong(nextIndex); |
|
} else { |
|
if (state.currentIndex < state.playlist.length - 1) { |
|
playSong(state.currentIndex + 1); |
|
} else { |
|
state.isPlaying = false; |
|
updatePlayButton(); |
|
} |
|
} |
|
} |
|
|
|
function setVolume(volume) { |
|
state.volume = volume; |
|
elements.audio.volume = volume; |
|
elements.volumeBar.style.height = `${volume * 100}%`; |
|
elements.volumeControl.classList.add('hidden'); |
|
} |
|
|
|
function updateVolumeIcon() { |
|
if (elements.audio.muted || elements.audio.volume === 0) { |
|
elements.volumeIcon.textContent = 'volume_off'; |
|
} else if (elements.audio.volume < 0.5) { |
|
elements.volumeIcon.textContent = 'volume_down'; |
|
} else { |
|
elements.volumeIcon.textContent = 'volume_up'; |
|
} |
|
} |
|
|
|
function setTimer(minutes) { |
|
if (state.timer) { |
|
clearTimeout(state.timer); |
|
state.timer = null; |
|
} |
|
if (minutes > 0) { |
|
state.timer = setTimeout(() => { |
|
elements.audio.pause(); |
|
state.isPlaying = false; |
|
updatePlayButton(); |
|
showNotification('Sleep timer stopped playback'); |
|
}, minutes * 60 * 1000); |
|
document.querySelectorAll('.timer-option').forEach(option => { |
|
option.classList.toggle('active', parseInt(option.dataset.minutes) === minutes); |
|
}); |
|
showNotification(`Sleep timer set for ${minutes} minutes`); |
|
} else { |
|
document.querySelectorAll('.timer-option').forEach(option => { |
|
option.classList.remove('active'); |
|
}); |
|
showNotification('Sleep timer disabled'); |
|
} |
|
elements.timerContainer.classList.add('hidden'); |
|
} |
|
|
|
function showNotification(message, isError = false) { |
|
elements.toast.textContent = message; |
|
elements.toast.className = `toast ${isError ? 'error' : ''} show`; |
|
setTimeout(() => { |
|
elements.toast.className = 'toast'; |
|
}, 3000); |
|
} |
|
|
|
|
|
function showUploadModal() { |
|
|
|
elements.uploadForm.reset(); |
|
elements.uploadModal.classList.remove('hidden'); |
|
} |
|
|
|
|
|
function hideUploadModal() { |
|
elements.uploadModal.classList.add('hidden'); |
|
} |
|
|
|
|
|
function handleUploadSubmit(event) { |
|
event.preventDefault(); |
|
|
|
if (elements.songFileInput.files.length === 0) { |
|
showNotification("No song file selected", true); |
|
return; |
|
} |
|
const songFile = elements.songFileInput.files[0]; |
|
const formData = new FormData(); |
|
formData.append('song', songFile); |
|
|
|
if (elements.coverFileInput.files.length > 0) { |
|
formData.append('cover', elements.coverFileInput.files[0]); |
|
} |
|
formData.append('title', elements.songTitleInput.value || songFile.name.replace('.mp3', '')); |
|
formData.append('artist', elements.songArtistInput.value || 'Uploaded Artist'); |
|
formData.append('lyrics', elements.songLyricsInput.value || ''); |
|
|
|
|
|
showNotification("Uploading..."); |
|
|
|
fetch('index.php?action=uploadSong', { |
|
method: 'POST', |
|
body: formData |
|
}) |
|
.then(response => response.json()) |
|
.then(result => { |
|
if (result.success) { |
|
showNotification(result.success); |
|
state.playlist.unshift(result.song); |
|
state.filteredPlaylist = [...state.playlist]; |
|
updatePlaylistUI(); |
|
elements.emptyState.style.display = 'none'; |
|
hideUploadModal(); |
|
} else { |
|
showNotification(result.error, true); |
|
} |
|
}) |
|
.catch(error => { |
|
console.error("Upload error:", error); |
|
showNotification("Upload failed", true); |
|
}); |
|
} |
|
|
|
|
|
function initVisualizer() { |
|
try { |
|
const AudioContext = window.AudioContext || window.webkitAudioContext; |
|
state.audioContext = new AudioContext(); |
|
state.analyser = state.audioContext.createAnalyser(); |
|
state.analyser.fftSize = 256; |
|
const source = state.audioContext.createMediaElementSource(elements.audio); |
|
source.connect(state.analyser); |
|
state.analyser.connect(state.audioContext.destination); |
|
const bufferLength = state.analyser.frequencyBinCount; |
|
state.dataArray = new Uint8Array(bufferLength); |
|
visualize(); |
|
} catch (e) { |
|
console.error('Audio visualization error:', e); |
|
} |
|
} |
|
|
|
|
|
function visualize() { |
|
if (!state.analyser) return; |
|
state.analyser.getByteFrequencyData(state.dataArray); |
|
const canvas = elements.visualizer; |
|
const ctx = canvas.getContext('2d'); |
|
const width = canvas.width = canvas.offsetWidth; |
|
const height = canvas.height = canvas.offsetHeight; |
|
ctx.clearRect(0, 0, width, height); |
|
const barWidth = (width / state.dataArray.length) * 2.5; |
|
let x = 0; |
|
for (let i = 0; i < state.dataArray.length; i++) { |
|
const barHeight = (state.dataArray[i] / 255) * height; |
|
ctx.fillStyle = `rgb(${50 + state.dataArray[i]}, 100, 200)`; |
|
ctx.fillRect(x, height - barHeight, barWidth, barHeight); |
|
x += barWidth + 1; |
|
} |
|
state.animationId = requestAnimationFrame(visualize); |
|
} |
|
|
|
init(); |
|
}); |
|
</script> |
|
</body> |
|
</html> |
|
|