druvx13 commited on
Commit
d215a9e
·
verified ·
1 Parent(s): bc826a2

Rename index.html to index.php

Browse files
Files changed (2) hide show
  1. index.html +0 -19
  2. 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>