Rename index.html to index.php
Browse files- index.html +0 -19
- index.php +731 -0
index.html
DELETED
@@ -1,19 +0,0 @@
|
|
1 |
-
<!doctype html>
|
2 |
-
<html>
|
3 |
-
<head>
|
4 |
-
<meta charset="utf-8" />
|
5 |
-
<meta name="viewport" content="width=device-width" />
|
6 |
-
<title>My static Space</title>
|
7 |
-
<link rel="stylesheet" href="style.css" />
|
8 |
-
</head>
|
9 |
-
<body>
|
10 |
-
<div class="card">
|
11 |
-
<h1>Welcome to your static Space!</h1>
|
12 |
-
<p>You can modify this app directly by editing <i>index.html</i> in the Files and versions tab.</p>
|
13 |
-
<p>
|
14 |
-
Also don't forget to check the
|
15 |
-
<a href="https://huggingface.co/docs/hub/spaces" target="_blank">Spaces documentation</a>.
|
16 |
-
</p>
|
17 |
-
</div>
|
18 |
-
</body>
|
19 |
-
</html>
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
index.php
ADDED
@@ -0,0 +1,731 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
<?php
|
2 |
+
// index.php
|
3 |
+
|
4 |
+
// Database configuration
|
5 |
+
$host = "sql111.byetcluster.com";
|
6 |
+
$db = "ssmm_32394582_music";
|
7 |
+
$user = "ssmm_32394582";
|
8 |
+
$pass = "13qrny65";
|
9 |
+
|
10 |
+
// Create connection using MySQLi
|
11 |
+
$conn = new mysqli($host, $user, $pass, $db);
|
12 |
+
if($conn->connect_error){
|
13 |
+
die("Connection failed: " . $conn->connect_error);
|
14 |
+
}
|
15 |
+
|
16 |
+
// API endpoints if "action" parameter is provided
|
17 |
+
if (isset($_GET['action'])) {
|
18 |
+
$action = $_GET['action'];
|
19 |
+
|
20 |
+
if ($action === 'getPlaylist') {
|
21 |
+
header("Content-Type: application/json");
|
22 |
+
$sql = "SELECT * FROM songs ORDER BY uploaded_at DESC";
|
23 |
+
$result = $conn->query($sql);
|
24 |
+
$songs = array();
|
25 |
+
while ($row = $result->fetch_assoc()) {
|
26 |
+
$songs[] = $row;
|
27 |
+
}
|
28 |
+
echo json_encode($songs);
|
29 |
+
$conn->close();
|
30 |
+
exit;
|
31 |
+
}
|
32 |
+
|
33 |
+
if ($action === 'uploadSong') {
|
34 |
+
header("Content-Type: application/json");
|
35 |
+
|
36 |
+
// Process the song file upload
|
37 |
+
if (isset($_FILES['song'])) {
|
38 |
+
$uploadDir = 'uploads/';
|
39 |
+
if (!is_dir($uploadDir)) {
|
40 |
+
mkdir($uploadDir, 0777, true);
|
41 |
+
}
|
42 |
+
|
43 |
+
$songName = basename($_FILES['song']['name']);
|
44 |
+
$targetSong = $uploadDir . $songName;
|
45 |
+
$songType = strtolower(pathinfo($targetSong, PATHINFO_EXTENSION));
|
46 |
+
|
47 |
+
// Allow only MP3 files
|
48 |
+
if ($songType !== "mp3") {
|
49 |
+
echo json_encode(array("error" => "Only MP3 files are allowed for the song."));
|
50 |
+
exit;
|
51 |
+
}
|
52 |
+
|
53 |
+
if (move_uploaded_file($_FILES['song']['tmp_name'], $targetSong)) {
|
54 |
+
$songURL = $targetSong;
|
55 |
+
} else {
|
56 |
+
echo json_encode(array("error" => "Error uploading the song file."));
|
57 |
+
exit;
|
58 |
+
}
|
59 |
+
} else {
|
60 |
+
echo json_encode(array("error" => "No song file received."));
|
61 |
+
exit;
|
62 |
+
}
|
63 |
+
|
64 |
+
// Process optional cover image upload
|
65 |
+
$coverURL = '';
|
66 |
+
if (isset($_FILES['cover']) && $_FILES['cover']['error'] == UPLOAD_ERR_OK) {
|
67 |
+
$coverName = basename($_FILES['cover']['name']);
|
68 |
+
$targetCover = $uploadDir . $coverName;
|
69 |
+
$coverType = strtolower(pathinfo($targetCover, PATHINFO_EXTENSION));
|
70 |
+
// Allow only specific image types
|
71 |
+
$allowed = array("jpg", "jpeg", "png", "gif");
|
72 |
+
if (!in_array($coverType, $allowed)) {
|
73 |
+
echo json_encode(array("error" => "Cover image must be jpg, jpeg, png, or gif."));
|
74 |
+
exit;
|
75 |
+
}
|
76 |
+
if (move_uploaded_file($_FILES['cover']['tmp_name'], $targetCover)) {
|
77 |
+
$coverURL = $targetCover;
|
78 |
+
}
|
79 |
+
}
|
80 |
+
|
81 |
+
// Get additional data from the POST request
|
82 |
+
$title = $conn->real_escape_string($_POST['title'] ?? $songName);
|
83 |
+
$artist = $conn->real_escape_string($_POST['artist'] ?? 'Uploaded Artist');
|
84 |
+
$lyrics = $conn->real_escape_string($_POST['lyrics'] ?? '');
|
85 |
+
|
86 |
+
$stmt = $conn->prepare("INSERT INTO songs (title, file, cover, artist, lyrics) VALUES (?, ?, ?, ?, ?)");
|
87 |
+
$stmt->bind_param("sssss", $title, $songURL, $coverURL, $artist, $lyrics);
|
88 |
+
|
89 |
+
if ($stmt->execute()){
|
90 |
+
echo json_encode(array(
|
91 |
+
"success" => "File uploaded successfully",
|
92 |
+
"song" => array(
|
93 |
+
"id" => $stmt->insert_id,
|
94 |
+
"title" => $title,
|
95 |
+
"file" => $songURL,
|
96 |
+
"cover" => $coverURL,
|
97 |
+
"artist" => $artist,
|
98 |
+
"lyrics" => $lyrics
|
99 |
+
)
|
100 |
+
));
|
101 |
+
} else {
|
102 |
+
echo json_encode(array("error" => "Database error: " . $conn->error));
|
103 |
+
}
|
104 |
+
$stmt->close();
|
105 |
+
$conn->close();
|
106 |
+
exit;
|
107 |
+
}
|
108 |
+
}
|
109 |
+
?>
|
110 |
+
<!DOCTYPE html>
|
111 |
+
<html lang="en">
|
112 |
+
<head>
|
113 |
+
<meta charset="UTF-8">
|
114 |
+
<meta name="viewport" content="width=device-width, initial-scale=1">
|
115 |
+
<title>Music Player</title>
|
116 |
+
<!-- Tailwind CSS CDN -->
|
117 |
+
<script src="https://cdn.tailwindcss.com"></script>
|
118 |
+
<!-- Material Icons -->
|
119 |
+
<link href="https://fonts.googleapis.com/icon?family=Material+Icons" rel="stylesheet">
|
120 |
+
<!-- External CSS file -->
|
121 |
+
<link rel="stylesheet" href="style.css">
|
122 |
+
</head>
|
123 |
+
<body class="bg-gray-900 text-white min-h-screen">
|
124 |
+
<!-- Main container -->
|
125 |
+
<div class="container mx-auto px-4 py-8">
|
126 |
+
<div class="flex flex-col lg:flex-row gap-8">
|
127 |
+
<!-- Playlist section -->
|
128 |
+
<div class="w-full lg:w-1/3 bg-gray-800 rounded-xl p-6 shadow-lg">
|
129 |
+
<div class="flex justify-between items-center mb-6">
|
130 |
+
<h2 class="text-2xl font-bold">Playlist</h2>
|
131 |
+
<div class="flex gap-2">
|
132 |
+
<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">
|
133 |
+
<i class="material-icons">refresh</i>
|
134 |
+
<span>Refresh</span>
|
135 |
+
</button>
|
136 |
+
<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">
|
137 |
+
<i class="material-icons">upload</i>
|
138 |
+
<span>Upload</span>
|
139 |
+
</button>
|
140 |
+
</div>
|
141 |
+
</div>
|
142 |
+
<div class="mb-4">
|
143 |
+
<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">
|
144 |
+
</div>
|
145 |
+
<div id="playlist" class="max-h-[500px] overflow-y-auto">
|
146 |
+
<!-- Playlist items will be added here -->
|
147 |
+
</div>
|
148 |
+
<div id="emptyState" class="text-center py-12 text-gray-400">
|
149 |
+
<i class="material-icons text-5xl mb-4">music_off</i>
|
150 |
+
<p class="text-lg mb-4">Your playlist is empty</p>
|
151 |
+
<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">
|
152 |
+
<i class="material-icons">upload</i>
|
153 |
+
<span>Upload MP3 files</span>
|
154 |
+
</button>
|
155 |
+
</div>
|
156 |
+
</div>
|
157 |
+
<!-- Player section -->
|
158 |
+
<div class="w-full lg:w-2/3">
|
159 |
+
<div class="player-container rounded-xl p-6 relative overflow-hidden">
|
160 |
+
<canvas class="visualizer" id="visualizer"></canvas>
|
161 |
+
<div class="flex flex-col md:flex-row gap-6 items-center">
|
162 |
+
<!-- Album art -->
|
163 |
+
<div class="album-art w-48 h-48 rounded-lg flex items-center justify-center">
|
164 |
+
<i class="material-icons text-6xl text-gray-500" id="albumArtIcon">music_note</i>
|
165 |
+
<img id="albumArtImage" src="" alt="Album Art" class="w-full h-full object-cover rounded-lg hidden">
|
166 |
+
</div>
|
167 |
+
<!-- Song info -->
|
168 |
+
<div class="flex-1">
|
169 |
+
<h1 class="text-2xl font-bold truncate" id="songTitle">No song selected</h1>
|
170 |
+
<p class="text-gray-400 mb-4" id="songArtist">Unknown artist</p>
|
171 |
+
<!-- Progress bar -->
|
172 |
+
<div class="mb-2">
|
173 |
+
<div class="progress-bar rounded-full w-full">
|
174 |
+
<div class="progress-filled rounded-full" id="progressBar" style="width: 0%"></div>
|
175 |
+
</div>
|
176 |
+
</div>
|
177 |
+
<div class="flex justify-between text-sm text-gray-400 mb-6">
|
178 |
+
<span id="currentTime">0:00</span>
|
179 |
+
<span id="durationTime">0:00</span>
|
180 |
+
</div>
|
181 |
+
<!-- Controls -->
|
182 |
+
<div class="flex items-center justify-between">
|
183 |
+
<div class="flex items-center gap-2">
|
184 |
+
<button id="shuffleBtn" class="control-btn" title="Shuffle">
|
185 |
+
<i class="material-icons">shuffle</i>
|
186 |
+
</button>
|
187 |
+
<button id="prevBtn" class="control-btn" title="Previous">
|
188 |
+
<i class="material-icons">skip_previous</i>
|
189 |
+
</button>
|
190 |
+
</div>
|
191 |
+
<button id="playBtn" class="control-btn bg-blue-600 hover:bg-blue-700 w-14 h-14" title="Play/Pause">
|
192 |
+
<i class="material-icons text-2xl" id="playIcon">play_arrow</i>
|
193 |
+
<i class="material-icons text-2xl hidden" id="pauseIcon">pause</i>
|
194 |
+
</button>
|
195 |
+
<div class="flex items-center gap-2">
|
196 |
+
<button id="nextBtn" class="control-btn" title="Next">
|
197 |
+
<i class="material-icons">skip_next</i>
|
198 |
+
</button>
|
199 |
+
<button id="repeatBtn" class="control-btn" title="Repeat">
|
200 |
+
<i class="material-icons">repeat</i>
|
201 |
+
</button>
|
202 |
+
</div>
|
203 |
+
</div>
|
204 |
+
</div>
|
205 |
+
</div>
|
206 |
+
<!-- Additional controls -->
|
207 |
+
<div class="flex justify-between items-center mt-6">
|
208 |
+
<div class="flex items-center gap-4">
|
209 |
+
<div class="relative">
|
210 |
+
<button id="volumeBtn" class="control-btn" title="Volume">
|
211 |
+
<i class="material-icons" id="volumeIcon">volume_up</i>
|
212 |
+
</button>
|
213 |
+
<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">
|
214 |
+
<div class="volume-bar rounded-full">
|
215 |
+
<div class="volume-filled rounded-full" id="volumeBar"></div>
|
216 |
+
</div>
|
217 |
+
</div>
|
218 |
+
</div>
|
219 |
+
<div class="relative">
|
220 |
+
<button id="timerBtn" class="control-btn" title="Sleep Timer">
|
221 |
+
<i class="material-icons">timer</i>
|
222 |
+
</button>
|
223 |
+
<div id="timerContainer" class="absolute bottom-full left-0 bg-gray-800 p-3 rounded-lg shadow-lg mb-2 w-40 hidden">
|
224 |
+
<div class="text-sm mb-2">Sleep Timer</div>
|
225 |
+
<div class="flex flex-col gap-1">
|
226 |
+
<div class="timer-option" data-minutes="0">Off</div>
|
227 |
+
<div class="timer-option" data-minutes="5">5 minutes</div>
|
228 |
+
<div class="timer-option" data-minutes="15">15 minutes</div>
|
229 |
+
<div class="timer-option" data-minutes="30">30 minutes</div>
|
230 |
+
<div class="timer-option" data-minutes="60">1 hour</div>
|
231 |
+
</div>
|
232 |
+
</div>
|
233 |
+
</div>
|
234 |
+
</div>
|
235 |
+
<div class="eq-bars" id="eqBars">
|
236 |
+
<div class="eq-bar"></div>
|
237 |
+
<div class="eq-bar"></div>
|
238 |
+
<div class="eq-bar"></div>
|
239 |
+
</div>
|
240 |
+
</div>
|
241 |
+
</div>
|
242 |
+
<!-- Lyrics section -->
|
243 |
+
<div class="mt-6 bg-gray-800 rounded-xl p-6">
|
244 |
+
<div class="flex justify-between items-center mb-4">
|
245 |
+
<h3 class="text-xl font-bold">Lyrics</h3>
|
246 |
+
</div>
|
247 |
+
<div id="lyricsContent" class="text-center text-gray-400">
|
248 |
+
No lyrics available for the current song
|
249 |
+
</div>
|
250 |
+
</div>
|
251 |
+
</div>
|
252 |
+
</div>
|
253 |
+
</div>
|
254 |
+
|
255 |
+
<!-- Toast notification -->
|
256 |
+
<div class="toast" id="toast"></div>
|
257 |
+
|
258 |
+
<!-- Upload form modal -->
|
259 |
+
<div id="uploadModal" class="fixed inset-0 flex items-center justify-center bg-black bg-opacity-50 hidden">
|
260 |
+
<div class="bg-gray-800 rounded-xl p-6 w-11/12 md:w-1/2 relative">
|
261 |
+
<h2 class="text-2xl mb-4">Upload New Song</h2>
|
262 |
+
<form id="uploadForm">
|
263 |
+
<div class="mb-4">
|
264 |
+
<label class="block mb-1">Song Title</label>
|
265 |
+
<input type="text" id="songTitleInput" class="w-full p-2 rounded bg-gray-700" placeholder="Enter song title">
|
266 |
+
</div>
|
267 |
+
<div class="mb-4">
|
268 |
+
<label class="block mb-1">Artist</label>
|
269 |
+
<input type="text" id="songArtistInput" class="w-full p-2 rounded bg-gray-700" placeholder="Enter artist name">
|
270 |
+
</div>
|
271 |
+
<div class="mb-4">
|
272 |
+
<label class="block mb-1">Cover Image (Optional)</label>
|
273 |
+
<input type="file" id="coverFileInput" accept="image/*" class="w-full p-2 rounded bg-gray-700">
|
274 |
+
</div>
|
275 |
+
<div class="mb-4">
|
276 |
+
<label class="block mb-1">Lyrics</label>
|
277 |
+
<textarea id="songLyricsInput" class="w-full p-2 rounded bg-gray-700" placeholder="Enter lyrics"></textarea>
|
278 |
+
</div>
|
279 |
+
<div class="mb-4">
|
280 |
+
<label class="block mb-1">Song File (MP3)</label>
|
281 |
+
<input type="file" id="songFileInput" accept=".mp3,audio/mp3" class="w-full p-2 rounded bg-gray-700">
|
282 |
+
</div>
|
283 |
+
<div class="flex justify-end gap-4">
|
284 |
+
<button type="button" id="cancelUploadBtn" class="bg-gray-700 hover:bg-gray-600 px-4 py-2 rounded">Cancel</button>
|
285 |
+
<button type="submit" id="submitUploadBtn" class="bg-blue-600 hover:bg-blue-700 px-4 py-2 rounded">Upload</button>
|
286 |
+
</div>
|
287 |
+
</form>
|
288 |
+
</div>
|
289 |
+
</div>
|
290 |
+
|
291 |
+
<script>
|
292 |
+
document.addEventListener('DOMContentLoaded', function() {
|
293 |
+
// Audio player state and DOM elements
|
294 |
+
const state = {
|
295 |
+
playlist: [],
|
296 |
+
filteredPlaylist: [],
|
297 |
+
currentIndex: -1,
|
298 |
+
isPlaying: false,
|
299 |
+
isShuffled: false,
|
300 |
+
isRepeating: false,
|
301 |
+
volume: 0.7,
|
302 |
+
timer: null,
|
303 |
+
searchQuery: '',
|
304 |
+
audioContext: null,
|
305 |
+
analyser: null,
|
306 |
+
dataArray: null,
|
307 |
+
animationId: null
|
308 |
+
};
|
309 |
+
|
310 |
+
const elements = {
|
311 |
+
audio: new Audio(),
|
312 |
+
playBtn: document.getElementById('playBtn'),
|
313 |
+
playIcon: document.getElementById('playIcon'),
|
314 |
+
pauseIcon: document.getElementById('pauseIcon'),
|
315 |
+
prevBtn: document.getElementById('prevBtn'),
|
316 |
+
nextBtn: document.getElementById('nextBtn'),
|
317 |
+
shuffleBtn: document.getElementById('shuffleBtn'),
|
318 |
+
repeatBtn: document.getElementById('repeatBtn'),
|
319 |
+
volumeBtn: document.getElementById('volumeBtn'),
|
320 |
+
volumeIcon: document.getElementById('volumeIcon'),
|
321 |
+
volumeControl: document.getElementById('volumeControl'),
|
322 |
+
volumeBar: document.getElementById('volumeBar'),
|
323 |
+
timerBtn: document.getElementById('timerBtn'),
|
324 |
+
timerContainer: document.getElementById('timerContainer'),
|
325 |
+
progressBar: document.getElementById('progressBar'),
|
326 |
+
currentTime: document.getElementById('currentTime'),
|
327 |
+
durationTime: document.getElementById('durationTime'),
|
328 |
+
songTitle: document.getElementById('songTitle'),
|
329 |
+
songArtist: document.getElementById('songArtist'),
|
330 |
+
albumArtIcon: document.getElementById('albumArtIcon'),
|
331 |
+
albumArtImage: document.getElementById('albumArtImage'),
|
332 |
+
playlist: document.getElementById('playlist'),
|
333 |
+
emptyState: document.getElementById('emptyState'),
|
334 |
+
uploadBtn: document.getElementById('uploadBtn'),
|
335 |
+
uploadBtnEmpty: document.getElementById('uploadBtnEmpty'),
|
336 |
+
refreshBtn: document.getElementById('refreshBtn'),
|
337 |
+
searchInput: document.getElementById('searchInput'),
|
338 |
+
lyricsContent: document.getElementById('lyricsContent'),
|
339 |
+
eqBars: document.getElementById('eqBars'),
|
340 |
+
visualizer: document.getElementById('visualizer'),
|
341 |
+
toast: document.getElementById('toast'),
|
342 |
+
uploadModal: document.getElementById('uploadModal'),
|
343 |
+
uploadForm: document.getElementById('uploadForm'),
|
344 |
+
cancelUploadBtn: document.getElementById('cancelUploadBtn'),
|
345 |
+
submitUploadBtn: document.getElementById('submitUploadBtn'),
|
346 |
+
songTitleInput: document.getElementById('songTitleInput'),
|
347 |
+
songArtistInput: document.getElementById('songArtistInput'),
|
348 |
+
songLyricsInput: document.getElementById('songLyricsInput'),
|
349 |
+
songFileInput: document.getElementById('songFileInput'),
|
350 |
+
coverFileInput: document.getElementById('coverFileInput')
|
351 |
+
};
|
352 |
+
|
353 |
+
// Initialize the player
|
354 |
+
function init() {
|
355 |
+
elements.audio.volume = state.volume;
|
356 |
+
elements.volumeBar.style.height = `${state.volume * 100}%`;
|
357 |
+
loadPlaylist();
|
358 |
+
setupEventListeners();
|
359 |
+
initVisualizer();
|
360 |
+
}
|
361 |
+
|
362 |
+
// Fetch playlist via AJAX from the PHP endpoint
|
363 |
+
function loadPlaylist() {
|
364 |
+
fetch('index.php?action=getPlaylist')
|
365 |
+
.then(response => response.json())
|
366 |
+
.then(data => {
|
367 |
+
if (Array.isArray(data)) {
|
368 |
+
state.playlist = data;
|
369 |
+
state.filteredPlaylist = [...data];
|
370 |
+
updatePlaylistUI();
|
371 |
+
elements.emptyState.style.display = data.length > 0 ? 'none' : 'block';
|
372 |
+
if(data.length > 0) {
|
373 |
+
state.currentIndex = 0;
|
374 |
+
}
|
375 |
+
} else {
|
376 |
+
console.error("Error fetching playlist:", data.error);
|
377 |
+
}
|
378 |
+
})
|
379 |
+
.catch(error => console.error("Fetch error:", error));
|
380 |
+
}
|
381 |
+
|
382 |
+
// Update the playlist UI
|
383 |
+
function updatePlaylistUI() {
|
384 |
+
elements.playlist.innerHTML = '';
|
385 |
+
if (state.filteredPlaylist.length === 0) {
|
386 |
+
const item = document.createElement('div');
|
387 |
+
item.className = 'text-center py-4 text-gray-400';
|
388 |
+
item.textContent = 'No songs found';
|
389 |
+
elements.playlist.appendChild(item);
|
390 |
+
return;
|
391 |
+
}
|
392 |
+
state.filteredPlaylist.forEach((song, index) => {
|
393 |
+
const item = document.createElement('div');
|
394 |
+
item.className = `flex items-center justify-between p-3 rounded-lg cursor-pointer playlist-item ${state.currentIndex === index ? 'current-song' : ''}`;
|
395 |
+
item.dataset.index = index;
|
396 |
+
item.innerHTML = `
|
397 |
+
<div class="flex items-center gap-3">
|
398 |
+
<div class="w-10 h-10 bg-gray-700 rounded flex items-center justify-center">
|
399 |
+
<i class="material-icons text-lg">music_note</i>
|
400 |
+
</div>
|
401 |
+
<div>
|
402 |
+
<div class="font-medium">${song.title}</div>
|
403 |
+
<div class="text-sm text-gray-400">${song.artist}</div>
|
404 |
+
</div>
|
405 |
+
</div>
|
406 |
+
<div class="text-gray-400 text-sm">3:45</div>
|
407 |
+
`;
|
408 |
+
item.addEventListener('click', () => playSong(index));
|
409 |
+
elements.playlist.appendChild(item);
|
410 |
+
});
|
411 |
+
}
|
412 |
+
|
413 |
+
// Play song by index
|
414 |
+
function playSong(index) {
|
415 |
+
if (index < 0 || index >= state.playlist.length) return;
|
416 |
+
state.currentIndex = index;
|
417 |
+
const song = state.playlist[index];
|
418 |
+
elements.songTitle.textContent = song.title;
|
419 |
+
elements.songArtist.textContent = song.artist;
|
420 |
+
if (song.cover) {
|
421 |
+
elements.albumArtIcon.style.display = 'none';
|
422 |
+
elements.albumArtImage.src = song.cover;
|
423 |
+
elements.albumArtImage.style.display = 'block';
|
424 |
+
} else {
|
425 |
+
elements.albumArtIcon.style.display = 'block';
|
426 |
+
elements.albumArtImage.style.display = 'none';
|
427 |
+
}
|
428 |
+
if (song.lyrics) {
|
429 |
+
elements.lyricsContent.innerHTML = song.lyrics.split('\n').map(line =>
|
430 |
+
`<div class="mb-2">${line}</div>`
|
431 |
+
).join('');
|
432 |
+
} else {
|
433 |
+
elements.lyricsContent.innerHTML = 'No lyrics available for this song';
|
434 |
+
}
|
435 |
+
elements.audio.src = song.file;
|
436 |
+
elements.audio.play().then(() => {
|
437 |
+
state.isPlaying = true;
|
438 |
+
updatePlayButton();
|
439 |
+
updatePlaylistUI();
|
440 |
+
}).catch(error => {
|
441 |
+
showNotification('Error playing song', true);
|
442 |
+
console.error('Play error:', error);
|
443 |
+
});
|
444 |
+
}
|
445 |
+
|
446 |
+
// Update play/pause button display
|
447 |
+
function updatePlayButton() {
|
448 |
+
if (state.isPlaying) {
|
449 |
+
elements.playIcon.classList.add('hidden');
|
450 |
+
elements.pauseIcon.classList.remove('hidden');
|
451 |
+
} else {
|
452 |
+
elements.playIcon.classList.remove('hidden');
|
453 |
+
elements.pauseIcon.classList.add('hidden');
|
454 |
+
}
|
455 |
+
}
|
456 |
+
|
457 |
+
// Setup event listeners
|
458 |
+
function setupEventListeners() {
|
459 |
+
elements.playBtn.addEventListener('click', () => {
|
460 |
+
if (state.currentIndex === -1 && state.playlist.length > 0) {
|
461 |
+
playSong(0);
|
462 |
+
} else if (state.isPlaying) {
|
463 |
+
elements.audio.pause();
|
464 |
+
state.isPlaying = false;
|
465 |
+
updatePlayButton();
|
466 |
+
} else {
|
467 |
+
elements.audio.play().then(() => {
|
468 |
+
state.isPlaying = true;
|
469 |
+
updatePlayButton();
|
470 |
+
});
|
471 |
+
}
|
472 |
+
});
|
473 |
+
elements.prevBtn.addEventListener('click', () => {
|
474 |
+
if (state.currentIndex > 0) {
|
475 |
+
playSong(state.currentIndex - 1);
|
476 |
+
} else {
|
477 |
+
playSong(state.playlist.length - 1);
|
478 |
+
}
|
479 |
+
});
|
480 |
+
elements.nextBtn.addEventListener('click', () => {
|
481 |
+
if (state.currentIndex < state.playlist.length - 1) {
|
482 |
+
playSong(state.currentIndex + 1);
|
483 |
+
} else {
|
484 |
+
playSong(0);
|
485 |
+
}
|
486 |
+
});
|
487 |
+
elements.shuffleBtn.addEventListener('click', () => {
|
488 |
+
state.isShuffled = !state.isShuffled;
|
489 |
+
elements.shuffleBtn.classList.toggle('active-btn', state.isShuffled);
|
490 |
+
showNotification(state.isShuffled ? 'Shuffle enabled' : 'Shuffle disabled');
|
491 |
+
});
|
492 |
+
elements.repeatBtn.addEventListener('click', () => {
|
493 |
+
state.isRepeating = !state.isRepeating;
|
494 |
+
elements.repeatBtn.classList.toggle('active-btn', state.isRepeating);
|
495 |
+
showNotification(state.isRepeating ? 'Repeat enabled' : 'Repeat disabled');
|
496 |
+
});
|
497 |
+
elements.volumeBtn.addEventListener('click', () => {
|
498 |
+
elements.volumeControl.classList.toggle('hidden');
|
499 |
+
});
|
500 |
+
elements.volumeBar.addEventListener('click', (e) => {
|
501 |
+
const rect = e.target.getBoundingClientRect();
|
502 |
+
const volume = 1 - (e.clientY - rect.top) / rect.height;
|
503 |
+
setVolume(Math.max(0, Math.min(1, volume)));
|
504 |
+
});
|
505 |
+
elements.timerBtn.addEventListener('click', () => {
|
506 |
+
elements.timerContainer.classList.toggle('hidden');
|
507 |
+
});
|
508 |
+
document.querySelectorAll('.timer-option').forEach(option => {
|
509 |
+
option.addEventListener('click', (e) => {
|
510 |
+
const minutes = parseInt(e.target.dataset.minutes);
|
511 |
+
setTimer(minutes);
|
512 |
+
});
|
513 |
+
});
|
514 |
+
elements.searchInput.addEventListener('input', (e) => {
|
515 |
+
state.searchQuery = e.target.value.toLowerCase();
|
516 |
+
filterPlaylist();
|
517 |
+
});
|
518 |
+
// Show the upload modal when upload buttons are clicked
|
519 |
+
elements.uploadBtn.addEventListener('click', showUploadModal);
|
520 |
+
elements.uploadBtnEmpty.addEventListener('click', showUploadModal);
|
521 |
+
// Cancel button hides the modal
|
522 |
+
elements.cancelUploadBtn.addEventListener('click', hideUploadModal);
|
523 |
+
// Handle form submission
|
524 |
+
elements.uploadForm.addEventListener('submit', handleUploadSubmit);
|
525 |
+
elements.refreshBtn.addEventListener('click', () => {
|
526 |
+
showNotification('Playlist refreshed');
|
527 |
+
loadPlaylist();
|
528 |
+
});
|
529 |
+
elements.audio.addEventListener('timeupdate', updateProgress);
|
530 |
+
elements.audio.addEventListener('ended', handleSongEnd);
|
531 |
+
elements.audio.addEventListener('durationchange', updateDuration);
|
532 |
+
elements.audio.addEventListener('volumechange', updateVolumeIcon);
|
533 |
+
}
|
534 |
+
|
535 |
+
function filterPlaylist() {
|
536 |
+
if (!state.searchQuery) {
|
537 |
+
state.filteredPlaylist = [...state.playlist];
|
538 |
+
} else {
|
539 |
+
state.filteredPlaylist = state.playlist.filter(song =>
|
540 |
+
song.title.toLowerCase().includes(state.searchQuery) ||
|
541 |
+
song.artist.toLowerCase().includes(state.searchQuery)
|
542 |
+
);
|
543 |
+
}
|
544 |
+
updatePlaylistUI();
|
545 |
+
}
|
546 |
+
|
547 |
+
function updateProgress() {
|
548 |
+
const currentTime = elements.audio.currentTime;
|
549 |
+
const duration = elements.audio.duration || 1;
|
550 |
+
const progressPercent = (currentTime / duration) * 100;
|
551 |
+
elements.progressBar.style.width = `${progressPercent}%`;
|
552 |
+
elements.currentTime.textContent = formatTime(currentTime);
|
553 |
+
}
|
554 |
+
|
555 |
+
function updateDuration() {
|
556 |
+
elements.durationTime.textContent = formatTime(elements.audio.duration || 0);
|
557 |
+
}
|
558 |
+
|
559 |
+
function formatTime(seconds) {
|
560 |
+
const mins = Math.floor(seconds / 60);
|
561 |
+
const secs = Math.floor(seconds % 60);
|
562 |
+
return `${mins}:${secs < 10 ? '0' : ''}${secs}`;
|
563 |
+
}
|
564 |
+
|
565 |
+
function handleSongEnd() {
|
566 |
+
if (state.isRepeating) {
|
567 |
+
elements.audio.currentTime = 0;
|
568 |
+
elements.audio.play();
|
569 |
+
} else if (state.isShuffled) {
|
570 |
+
const nextIndex = Math.floor(Math.random() * state.playlist.length);
|
571 |
+
playSong(nextIndex);
|
572 |
+
} else {
|
573 |
+
if (state.currentIndex < state.playlist.length - 1) {
|
574 |
+
playSong(state.currentIndex + 1);
|
575 |
+
} else {
|
576 |
+
state.isPlaying = false;
|
577 |
+
updatePlayButton();
|
578 |
+
}
|
579 |
+
}
|
580 |
+
}
|
581 |
+
|
582 |
+
function setVolume(volume) {
|
583 |
+
state.volume = volume;
|
584 |
+
elements.audio.volume = volume;
|
585 |
+
elements.volumeBar.style.height = `${volume * 100}%`;
|
586 |
+
elements.volumeControl.classList.add('hidden');
|
587 |
+
}
|
588 |
+
|
589 |
+
function updateVolumeIcon() {
|
590 |
+
if (elements.audio.muted || elements.audio.volume === 0) {
|
591 |
+
elements.volumeIcon.textContent = 'volume_off';
|
592 |
+
} else if (elements.audio.volume < 0.5) {
|
593 |
+
elements.volumeIcon.textContent = 'volume_down';
|
594 |
+
} else {
|
595 |
+
elements.volumeIcon.textContent = 'volume_up';
|
596 |
+
}
|
597 |
+
}
|
598 |
+
|
599 |
+
function setTimer(minutes) {
|
600 |
+
if (state.timer) {
|
601 |
+
clearTimeout(state.timer);
|
602 |
+
state.timer = null;
|
603 |
+
}
|
604 |
+
if (minutes > 0) {
|
605 |
+
state.timer = setTimeout(() => {
|
606 |
+
elements.audio.pause();
|
607 |
+
state.isPlaying = false;
|
608 |
+
updatePlayButton();
|
609 |
+
showNotification('Sleep timer stopped playback');
|
610 |
+
}, minutes * 60 * 1000);
|
611 |
+
document.querySelectorAll('.timer-option').forEach(option => {
|
612 |
+
option.classList.toggle('active', parseInt(option.dataset.minutes) === minutes);
|
613 |
+
});
|
614 |
+
showNotification(`Sleep timer set for ${minutes} minutes`);
|
615 |
+
} else {
|
616 |
+
document.querySelectorAll('.timer-option').forEach(option => {
|
617 |
+
option.classList.remove('active');
|
618 |
+
});
|
619 |
+
showNotification('Sleep timer disabled');
|
620 |
+
}
|
621 |
+
elements.timerContainer.classList.add('hidden');
|
622 |
+
}
|
623 |
+
|
624 |
+
function showNotification(message, isError = false) {
|
625 |
+
elements.toast.textContent = message;
|
626 |
+
elements.toast.className = `toast ${isError ? 'error' : ''} show`;
|
627 |
+
setTimeout(() => {
|
628 |
+
elements.toast.className = 'toast';
|
629 |
+
}, 3000);
|
630 |
+
}
|
631 |
+
|
632 |
+
// Show the upload modal form
|
633 |
+
function showUploadModal() {
|
634 |
+
// Reset form fields
|
635 |
+
elements.uploadForm.reset();
|
636 |
+
elements.uploadModal.classList.remove('hidden');
|
637 |
+
}
|
638 |
+
|
639 |
+
// Hide the upload modal form
|
640 |
+
function hideUploadModal() {
|
641 |
+
elements.uploadModal.classList.add('hidden');
|
642 |
+
}
|
643 |
+
|
644 |
+
// Handle form submission and file upload
|
645 |
+
function handleUploadSubmit(event) {
|
646 |
+
event.preventDefault();
|
647 |
+
// Check that a song file is selected
|
648 |
+
if (elements.songFileInput.files.length === 0) {
|
649 |
+
showNotification("No song file selected", true);
|
650 |
+
return;
|
651 |
+
}
|
652 |
+
const songFile = elements.songFileInput.files[0];
|
653 |
+
const formData = new FormData();
|
654 |
+
formData.append('song', songFile);
|
655 |
+
// Append cover file if provided
|
656 |
+
if (elements.coverFileInput.files.length > 0) {
|
657 |
+
formData.append('cover', elements.coverFileInput.files[0]);
|
658 |
+
}
|
659 |
+
formData.append('title', elements.songTitleInput.value || songFile.name.replace('.mp3', ''));
|
660 |
+
formData.append('artist', elements.songArtistInput.value || 'Uploaded Artist');
|
661 |
+
formData.append('lyrics', elements.songLyricsInput.value || '');
|
662 |
+
|
663 |
+
// Show uploading process
|
664 |
+
showNotification("Uploading...");
|
665 |
+
|
666 |
+
fetch('index.php?action=uploadSong', {
|
667 |
+
method: 'POST',
|
668 |
+
body: formData
|
669 |
+
})
|
670 |
+
.then(response => response.json())
|
671 |
+
.then(result => {
|
672 |
+
if (result.success) {
|
673 |
+
showNotification(result.success);
|
674 |
+
state.playlist.unshift(result.song);
|
675 |
+
state.filteredPlaylist = [...state.playlist];
|
676 |
+
updatePlaylistUI();
|
677 |
+
elements.emptyState.style.display = 'none';
|
678 |
+
hideUploadModal();
|
679 |
+
} else {
|
680 |
+
showNotification(result.error, true);
|
681 |
+
}
|
682 |
+
})
|
683 |
+
.catch(error => {
|
684 |
+
console.error("Upload error:", error);
|
685 |
+
showNotification("Upload failed", true);
|
686 |
+
});
|
687 |
+
}
|
688 |
+
|
689 |
+
// Initialize visualizer
|
690 |
+
function initVisualizer() {
|
691 |
+
try {
|
692 |
+
const AudioContext = window.AudioContext || window.webkitAudioContext;
|
693 |
+
state.audioContext = new AudioContext();
|
694 |
+
state.analyser = state.audioContext.createAnalyser();
|
695 |
+
state.analyser.fftSize = 256;
|
696 |
+
const source = state.audioContext.createMediaElementSource(elements.audio);
|
697 |
+
source.connect(state.analyser);
|
698 |
+
state.analyser.connect(state.audioContext.destination);
|
699 |
+
const bufferLength = state.analyser.frequencyBinCount;
|
700 |
+
state.dataArray = new Uint8Array(bufferLength);
|
701 |
+
visualize();
|
702 |
+
} catch (e) {
|
703 |
+
console.error('Audio visualization error:', e);
|
704 |
+
}
|
705 |
+
}
|
706 |
+
|
707 |
+
// Visualize audio on canvas
|
708 |
+
function visualize() {
|
709 |
+
if (!state.analyser) return;
|
710 |
+
state.analyser.getByteFrequencyData(state.dataArray);
|
711 |
+
const canvas = elements.visualizer;
|
712 |
+
const ctx = canvas.getContext('2d');
|
713 |
+
const width = canvas.width = canvas.offsetWidth;
|
714 |
+
const height = canvas.height = canvas.offsetHeight;
|
715 |
+
ctx.clearRect(0, 0, width, height);
|
716 |
+
const barWidth = (width / state.dataArray.length) * 2.5;
|
717 |
+
let x = 0;
|
718 |
+
for (let i = 0; i < state.dataArray.length; i++) {
|
719 |
+
const barHeight = (state.dataArray[i] / 255) * height;
|
720 |
+
ctx.fillStyle = `rgb(${50 + state.dataArray[i]}, 100, 200)`;
|
721 |
+
ctx.fillRect(x, height - barHeight, barWidth, barHeight);
|
722 |
+
x += barWidth + 1;
|
723 |
+
}
|
724 |
+
state.animationId = requestAnimationFrame(visualize);
|
725 |
+
}
|
726 |
+
|
727 |
+
init();
|
728 |
+
});
|
729 |
+
</script>
|
730 |
+
</body>
|
731 |
+
</html>
|