MySafeCode commited on
Commit
ac29d9e
·
verified ·
1 Parent(s): 8a195a1

Update index.html

Browse files
Files changed (1) hide show
  1. index.html +630 -19
index.html CHANGED
@@ -1,19 +1,630 @@
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>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <!DOCTYPE html>
2
+ <html lang="en">
3
+ <head>
4
+ <meta charset="UTF-8">
5
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
6
+ <title>1hit.no Playlist Player</title>
7
+ <style>
8
+ * {
9
+ margin: 0;
10
+ padding: 0;
11
+ box-sizing: border-box;
12
+ }
13
+
14
+ body {
15
+ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, sans-serif;
16
+ background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
17
+ min-height: 100vh;
18
+ padding: 20px;
19
+ color: #333;
20
+ }
21
+
22
+ .container {
23
+ max-width: 900px;
24
+ margin: 0 auto;
25
+ background: white;
26
+ border-radius: 20px;
27
+ box-shadow: 0 20px 60px rgba(0,0,0,0.3);
28
+ overflow: hidden;
29
+ }
30
+
31
+ header {
32
+ background: linear-gradient(135deg, #6a11cb 0%, #2575fc 100%);
33
+ color: white;
34
+ padding: 30px;
35
+ text-align: center;
36
+ }
37
+
38
+ h1 {
39
+ font-size: 2.5em;
40
+ margin-bottom: 10px;
41
+ }
42
+
43
+ .controls {
44
+ display: flex;
45
+ gap: 15px;
46
+ justify-content: center;
47
+ padding: 20px;
48
+ background: #f5f5f5;
49
+ border-bottom: 1px solid #e0e0e0;
50
+ }
51
+
52
+ .btn {
53
+ padding: 10px 20px;
54
+ border: none;
55
+ border-radius: 50px;
56
+ background: linear-gradient(135deg, #6a11cb 0%, #2575fc 100%);
57
+ color: white;
58
+ cursor: pointer;
59
+ font-weight: 600;
60
+ transition: transform 0.2s, box-shadow 0.2s;
61
+ }
62
+
63
+ .btn:hover {
64
+ transform: translateY(-2px);
65
+ box-shadow: 0 5px 15px rgba(106, 17, 203, 0.4);
66
+ }
67
+
68
+ .btn.active {
69
+ background: linear-gradient(135deg, #2575fc 0%, #6a11cb 100%);
70
+ box-shadow: 0 0 0 3px rgba(106, 17, 203, 0.2);
71
+ }
72
+
73
+ .main-content {
74
+ display: grid;
75
+ grid-template-columns: 1fr 1fr;
76
+ gap: 0;
77
+ min-height: 500px;
78
+ }
79
+
80
+ @media (max-width: 768px) {
81
+ .main-content {
82
+ grid-template-columns: 1fr;
83
+ }
84
+ }
85
+
86
+ .playlist-container {
87
+ border-right: 1px solid #e0e0e0;
88
+ display: flex;
89
+ flex-direction: column;
90
+ }
91
+
92
+ .playlist-header {
93
+ padding: 20px;
94
+ background: #f9f9f9;
95
+ border-bottom: 1px solid #e0e0e0;
96
+ }
97
+
98
+ .playlist {
99
+ flex: 1;
100
+ overflow-y: auto;
101
+ max-height: 400px;
102
+ }
103
+
104
+ .playlist-item {
105
+ padding: 15px 20px;
106
+ border-bottom: 1px solid #eee;
107
+ cursor: pointer;
108
+ transition: background-color 0.2s;
109
+ display: flex;
110
+ align-items: center;
111
+ gap: 15px;
112
+ }
113
+
114
+ .playlist-item:hover {
115
+ background-color: #f5f5f5;
116
+ }
117
+
118
+ .playlist-item.active {
119
+ background: linear-gradient(135deg, rgba(106, 17, 203, 0.1) 0%, rgba(37, 117, 252, 0.1) 100%);
120
+ border-left: 4px solid #6a11cb;
121
+ }
122
+
123
+ .track-image {
124
+ width: 50px;
125
+ height: 50px;
126
+ border-radius: 8px;
127
+ object-fit: cover;
128
+ background: #e0e0e0;
129
+ }
130
+
131
+ .track-info {
132
+ flex: 1;
133
+ }
134
+
135
+ .track-title {
136
+ font-weight: 600;
137
+ margin-bottom: 5px;
138
+ }
139
+
140
+ .track-details {
141
+ font-size: 0.9em;
142
+ color: #666;
143
+ }
144
+
145
+ .player-container {
146
+ display: flex;
147
+ flex-direction: column;
148
+ padding: 30px;
149
+ background: #fafafa;
150
+ }
151
+
152
+ .current-track {
153
+ text-align: center;
154
+ margin-bottom: 30px;
155
+ }
156
+
157
+ .current-image {
158
+ width: 200px;
159
+ height: 200px;
160
+ border-radius: 15px;
161
+ object-fit: cover;
162
+ margin: 0 auto 20px;
163
+ box-shadow: 0 10px 30px rgba(0,0,0,0.2);
164
+ background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
165
+ }
166
+
167
+ .current-title {
168
+ font-size: 1.5em;
169
+ font-weight: 700;
170
+ margin-bottom: 10px;
171
+ color: #333;
172
+ }
173
+
174
+ .current-artist {
175
+ color: #666;
176
+ font-size: 1.1em;
177
+ }
178
+
179
+ .player-controls {
180
+ display: flex;
181
+ flex-direction: column;
182
+ gap: 20px;
183
+ align-items: center;
184
+ }
185
+
186
+ .progress-container {
187
+ width: 100%;
188
+ margin: 20px 0;
189
+ }
190
+
191
+ .progress-bar {
192
+ width: 100%;
193
+ height: 6px;
194
+ background: #e0e0e0;
195
+ border-radius: 3px;
196
+ cursor: pointer;
197
+ overflow: hidden;
198
+ }
199
+
200
+ .progress {
201
+ height: 100%;
202
+ background: linear-gradient(135deg, #6a11cb 0%, #2575fc 100%);
203
+ width: 0%;
204
+ border-radius: 3px;
205
+ transition: width 0.1s;
206
+ }
207
+
208
+ .time-display {
209
+ display: flex;
210
+ justify-content: space-between;
211
+ font-size: 0.9em;
212
+ color: #666;
213
+ margin-top: 5px;
214
+ }
215
+
216
+ .control-buttons {
217
+ display: flex;
218
+ gap: 20px;
219
+ align-items: center;
220
+ }
221
+
222
+ .control-btn {
223
+ background: none;
224
+ border: none;
225
+ cursor: pointer;
226
+ padding: 10px;
227
+ border-radius: 50%;
228
+ transition: background-color 0.2s;
229
+ display: flex;
230
+ align-items: center;
231
+ justify-content: center;
232
+ }
233
+
234
+ .control-btn:hover {
235
+ background-color: rgba(106, 17, 203, 0.1);
236
+ }
237
+
238
+ .play-pause {
239
+ width: 60px;
240
+ height: 60px;
241
+ background: linear-gradient(135deg, #6a11cb 0%, #2575fc 100%);
242
+ color: white;
243
+ }
244
+
245
+ .play-pause:hover {
246
+ transform: scale(1.05);
247
+ }
248
+
249
+ .lyrics-container {
250
+ margin-top: 30px;
251
+ background: white;
252
+ border-radius: 10px;
253
+ padding: 20px;
254
+ max-height: 200px;
255
+ overflow-y: auto;
256
+ border: 1px solid #e0e0e0;
257
+ }
258
+
259
+ .lyrics-title {
260
+ font-weight: 600;
261
+ margin-bottom: 15px;
262
+ color: #333;
263
+ }
264
+
265
+ .lyrics-content {
266
+ line-height: 1.6;
267
+ color: #555;
268
+ white-space: pre-wrap;
269
+ }
270
+
271
+ .status {
272
+ text-align: center;
273
+ padding: 20px;
274
+ color: #666;
275
+ }
276
+
277
+ .loading {
278
+ display: inline-block;
279
+ width: 20px;
280
+ height: 20px;
281
+ border: 3px solid #e0e0e0;
282
+ border-top-color: #6a11cb;
283
+ border-radius: 50%;
284
+ animation: spin 1s linear infinite;
285
+ }
286
+
287
+ @keyframes spin {
288
+ to { transform: rotate(360deg); }
289
+ }
290
+ </style>
291
+ </head>
292
+ <body>
293
+ <div class="container">
294
+ <header>
295
+ <h1>🎵 1hit.no Playlist</h1>
296
+ <p>Stream your favorite tracks</p>
297
+ </header>
298
+
299
+ <div class="controls">
300
+ <button id="shuffleBtn" class="btn">🔀 Shuffle</button>
301
+ <button id="repeatBtn" class="btn">🔁 Repeat Off</button>
302
+ <button id="refreshBtn" class="btn">🔄 Refresh Playlist</button>
303
+ </div>
304
+
305
+ <div class="main-content">
306
+ <div class="playlist-container">
307
+ <div class="playlist-header">
308
+ <h2>Playlist (<span id="trackCount">0</span> tracks)</h2>
309
+ </div>
310
+ <div id="playlist" class="playlist">
311
+ <!-- Playlist items will be added here -->
312
+ </div>
313
+ </div>
314
+
315
+ <div class="player-container">
316
+ <div class="current-track">
317
+ <img id="currentImage" class="current-image" src="data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iMjAwIiBoZWlnaHQ9IjIwMCIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIj48cmVjdCB3aWR0aD0iMjAwIiBoZWlnaHQ9IjIwMCIgZmlsbD0idXJsKCNncmFkaWVudCkiLz48dGV4dCB4PSIxMDAiIHk9IjEwMCIgZm9udC1mYW1pbHk9IkFyaWFsIiBmb250LXNpemU9IjE0IiBmaWxsPSJ3aGl0ZSIgdGV4dC1hbmNob3I9Im1pZGRsZSIgZHk9Ii4zZW0iPk5vIFRyYWNrIFNlbGVjdGVkPC90ZXh0PjxsaW5lYXJHcmFkaWVudCBpZD0iZ3JhZGllbnQiPjxzdG9wIG9mZnNldD0iMCIgc3RvcC1jb2xvcj0iIzY2N2VlYSIvPjxzdG9wIG9mZnNldD0iMTAwJSIgc3RvcC1jb2xvcj0iIzc2NGJhMiIvPjwvbGluZWFyR3JhZGllbnQ+PC9zdmc+">
318
+ <h2 id="currentTitle" class="current-title">No Track Selected</h2>
319
+ <p id="currentArtist" class="current-artist">Select a track from the playlist</p>
320
+ </div>
321
+
322
+ <div class="player-controls">
323
+ <div class="progress-container">
324
+ <div class="progress-bar" id="progressBar">
325
+ <div class="progress" id="progress"></div>
326
+ </div>
327
+ <div class="time-display">
328
+ <span id="currentTime">0:00</span>
329
+ <span id="duration">0:00</span>
330
+ </div>
331
+ </div>
332
+
333
+ <div class="control-buttons">
334
+ <button class="control-btn" id="prevBtn">⏮</button>
335
+ <button class="control-btn play-pause" id="playPauseBtn">▶</button>
336
+ <button class="control-btn" id="nextBtn">⏭</button>
337
+ </div>
338
+ </div>
339
+
340
+ <div class="lyrics-container">
341
+ <div class="lyrics-title">Lyrics</div>
342
+ <div id="lyricsContent" class="lyrics-content">
343
+ Lyrics will appear here when available...
344
+ </div>
345
+ </div>
346
+ </div>
347
+ </div>
348
+
349
+ <div id="status" class="status">
350
+ <span class="loading"></span> Loading playlist...
351
+ </div>
352
+ </div>
353
+
354
+ <script>
355
+ class PlaylistPlayer {
356
+ constructor() {
357
+ this.playlist = [];
358
+ this.currentTrackIndex = -1;
359
+ this.isPlaying = false;
360
+ this.isShuffled = false;
361
+ this.isRepeating = false;
362
+ this.originalPlaylist = [];
363
+
364
+ this.audio = new Audio();
365
+ this.audio.crossOrigin = "anonymous";
366
+
367
+ this.initElements();
368
+ this.initEventListeners();
369
+ this.loadPlaylist();
370
+ }
371
+
372
+ initElements() {
373
+ this.elements = {
374
+ playlist: document.getElementById('playlist'),
375
+ currentImage: document.getElementById('currentImage'),
376
+ currentTitle: document.getElementById('currentTitle'),
377
+ currentArtist: document.getElementById('currentArtist'),
378
+ playPauseBtn: document.getElementById('playPauseBtn'),
379
+ prevBtn: document.getElementById('prevBtn'),
380
+ nextBtn: document.getElementById('nextBtn'),
381
+ progressBar: document.getElementById('progressBar'),
382
+ progress: document.getElementById('progress'),
383
+ currentTime: document.getElementById('currentTime'),
384
+ duration: document.getElementById('duration'),
385
+ lyricsContent: document.getElementById('lyricsContent'),
386
+ shuffleBtn: document.getElementById('shuffleBtn'),
387
+ repeatBtn: document.getElementById('repeatBtn'),
388
+ refreshBtn: document.getElementById('refreshBtn'),
389
+ trackCount: document.getElementById('trackCount'),
390
+ status: document.getElementById('status')
391
+ };
392
+ }
393
+
394
+ initEventListeners() {
395
+ this.elements.playPauseBtn.addEventListener('click', () => this.togglePlay());
396
+ this.elements.prevBtn.addEventListener('click', () => this.prevTrack());
397
+ this.elements.nextBtn.addEventListener('click', () => this.nextTrack());
398
+ this.elements.progressBar.addEventListener('click', (e) => this.seek(e));
399
+
400
+ this.elements.shuffleBtn.addEventListener('click', () => this.toggleShuffle());
401
+ this.elements.repeatBtn.addEventListener('click', () => this.toggleRepeat());
402
+ this.elements.refreshBtn.addEventListener('click', () => this.loadPlaylist());
403
+
404
+ this.audio.addEventListener('timeupdate', () => this.updateProgress());
405
+ this.audio.addEventListener('ended', () => this.nextTrack());
406
+ this.audio.addEventListener('loadedmetadata', () => this.updateDuration());
407
+ this.audio.addEventListener('error', (e) => console.error('Audio error:', e));
408
+ }
409
+
410
+ async loadPlaylist() {
411
+ try {
412
+ this.elements.status.innerHTML = '<span class="loading"></span> Loading playlist...';
413
+
414
+ // Load database.json
415
+ const response = await fetch('https://1hit.no/gen/database.json');
416
+ const database = await response.json();
417
+
418
+ this.playlist = [];
419
+ this.originalPlaylist = [];
420
+
421
+ // Process each track in database
422
+ for (const [taskId, trackData] of Object.entries(database)) {
423
+ try {
424
+ // Load callback JSON for this track
425
+ const callbackResponse = await fetch(`https://1hit.no/gen/callbacks/${taskId}.json`);
426
+ const callbackData = await callbackResponse.json();
427
+
428
+ const track = {
429
+ id: taskId,
430
+ url: trackData.url || '',
431
+ title: callbackData.title || 'Unknown Title',
432
+ artist: callbackData.artist || 'Unknown Artist',
433
+ image: callbackData.image || null,
434
+ lyrics: callbackData.lyrics || null,
435
+ duration: callbackData.duration || 0
436
+ };
437
+
438
+ this.playlist.push(track);
439
+ this.originalPlaylist.push(track);
440
+
441
+ } catch (error) {
442
+ console.warn(`Failed to load track ${taskId}:`, error);
443
+ }
444
+ }
445
+
446
+ this.renderPlaylist();
447
+ this.elements.trackCount.textContent = this.playlist.length;
448
+ this.elements.status.textContent = `Loaded ${this.playlist.length} tracks`;
449
+
450
+ if (this.playlist.length > 0) {
451
+ this.selectTrack(0);
452
+ }
453
+
454
+ } catch (error) {
455
+ console.error('Failed to load playlist:', error);
456
+ this.elements.status.textContent = 'Error loading playlist';
457
+ }
458
+ }
459
+
460
+ renderPlaylist() {
461
+ this.elements.playlist.innerHTML = '';
462
+
463
+ this.playlist.forEach((track, index) => {
464
+ const item = document.createElement('div');
465
+ item.className = `playlist-item ${index === this.currentTrackIndex ? 'active' : ''}`;
466
+ item.innerHTML = `
467
+ <img src="${track.image || 'data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iNTAiIGhlaWdodD0iNTAiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyI+PHJlY3Qgd2lkdGg9IjUwIiBoZWlnaHQ9IjUwIiBmaWxsPSIjNjY3ZWVhIi8+PHRleHQgeD0iMjUiIHk9IjI1IiBmb250LWZhbWlseT0iQXJpYWwiIGZvbnQtc2l6ZT0iMTAiIGZpbGw9IndoaXRlIiB0ZXh0LWFuY2hvcj0ibWlkZGxlIiBkeT0iLjNlbSI+Tk88L3RleHQ+PC9zdmc+'}"
468
+ alt="${track.title}" class="track-image">
469
+ <div class="track-info">
470
+ <div class="track-title">${track.title}</div>
471
+ <div class="track-details">${track.artist}</div>
472
+ </div>
473
+ `;
474
+
475
+ item.addEventListener('click', () => this.selectTrack(index));
476
+ this.elements.playlist.appendChild(item);
477
+ });
478
+ }
479
+
480
+ selectTrack(index) {
481
+ if (index < 0 || index >= this.playlist.length) return;
482
+
483
+ this.currentTrackIndex = index;
484
+ const track = this.playlist[index];
485
+
486
+ // Update UI
487
+ this.elements.currentTitle.textContent = track.title;
488
+ this.elements.currentArtist.textContent = track.artist;
489
+ this.elements.currentImage.src = track.image || 'data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iMjAwIiBoZWlnaHQ9IjIwMCIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIj48cmVjdCB3aWR0aD0iMjAwIiBoZWlnaHQ9IjIwMCIgZmlsbD0idXJsKCNncmFkaWVudCkiLz48dGV4dCB4PSIxMDAiIHk9IjEwMCIgZm9udC1mYW1pbHk9IkFyaWFsIiBmb250LXNpemU9IjE0IiBmaWxsPSJ3aGl0ZSIgdGV4dC1hbmNob3I9Im1pZGRsZSIgZHk9Ii4zZW0iPiR7dHJhY2sudGl0bGV9PC90ZXh0PjxsaW5lYXJHcmFkaWVudCBpZD0iZ3JhZGllbnQiPjxzdG9wIG9mZnNldD0iMCIgc3RvcC1jb2xvcj0iIzY2N2VlYSIvPjxzdG9wIG9mZnNldD0iMTAwJSIgc3RvcC1jb2xvcj0iIzc2NGJhMiIvPjwvbGluZWFyR3JhZGllbnQ+PC9zdmc+';
490
+ this.elements.lyricsContent.textContent = track.lyrics || 'No lyrics available for this track.';
491
+
492
+ // Update playlist highlights
493
+ this.renderPlaylist();
494
+
495
+ // Load and play audio
496
+ this.audio.src = track.url;
497
+ this.audio.load();
498
+ this.play();
499
+ }
500
+
501
+ togglePlay() {
502
+ if (!this.audio.src) return;
503
+
504
+ if (this.isPlaying) {
505
+ this.pause();
506
+ } else {
507
+ this.play();
508
+ }
509
+ }
510
+
511
+ play() {
512
+ if (!this.audio.src && this.playlist.length > 0) {
513
+ this.selectTrack(0);
514
+ }
515
+
516
+ this.audio.play()
517
+ .then(() => {
518
+ this.isPlaying = true;
519
+ this.elements.playPauseBtn.textContent = '⏸';
520
+ })
521
+ .catch(error => {
522
+ console.error('Playback failed:', error);
523
+ this.elements.status.textContent = 'Playback failed. Trying next track...';
524
+ setTimeout(() => this.nextTrack(), 1000);
525
+ });
526
+ }
527
+
528
+ pause() {
529
+ this.audio.pause();
530
+ this.isPlaying = false;
531
+ this.elements.playPauseBtn.textContent = '▶';
532
+ }
533
+
534
+ nextTrack() {
535
+ if (this.playlist.length === 0) return;
536
+
537
+ let nextIndex = this.currentTrackIndex + 1;
538
+
539
+ if (nextIndex >= this.playlist.length) {
540
+ if (this.isRepeating) {
541
+ nextIndex = 0;
542
+ } else {
543
+ return; // End of playlist
544
+ }
545
+ }
546
+
547
+ this.selectTrack(nextIndex);
548
+ }
549
+
550
+ prevTrack() {
551
+ if (this.playlist.length === 0 || this.currentTrackIndex <= 0) return;
552
+
553
+ this.selectTrack(this.currentTrackIndex - 1);
554
+ }
555
+
556
+ toggleShuffle() {
557
+ this.isShuffled = !this.isShuffled;
558
+
559
+ if (this.isShuffled) {
560
+ // Shuffle the playlist
561
+ const currentTrack = this.playlist[this.currentTrackIndex];
562
+ const shuffled = [...this.playlist];
563
+ for (let i = shuffled.length - 1; i > 0; i--) {
564
+ const j = Math.floor(Math.random() * (i + 1));
565
+ [shuffled[i], shuffled[j]] = [shuffled[j], shuffled[i]];
566
+ }
567
+
568
+ // Keep current track at the same position
569
+ const currentIndex = shuffled.findIndex(track => track.id === currentTrack.id);
570
+ if (currentIndex > 0) {
571
+ [shuffled[0], shuffled[currentIndex]] = [shuffled[currentIndex], shuffled[0]];
572
+ }
573
+
574
+ this.playlist = shuffled;
575
+ this.currentTrackIndex = 0;
576
+ this.elements.shuffleBtn.classList.add('active');
577
+ } else {
578
+ // Restore original order
579
+ const currentTrack = this.playlist[this.currentTrackIndex];
580
+ this.playlist = [...this.originalPlaylist];
581
+ this.currentTrackIndex = this.playlist.findIndex(track => track.id === currentTrack.id);
582
+ this.elements.shuffleBtn.classList.remove('active');
583
+ }
584
+
585
+ this.renderPlaylist();
586
+ }
587
+
588
+ toggleRepeat() {
589
+ this.isRepeating = !this.isRepeating;
590
+ this.elements.repeatBtn.textContent = this.isRepeating ? '🔁 Repeat On' : '🔁 Repeat Off';
591
+ this.elements.repeatBtn.classList.toggle('active', this.isRepeating);
592
+ }
593
+
594
+ updateProgress() {
595
+ if (!isNaN(this.audio.duration)) {
596
+ const progress = (this.audio.currentTime / this.audio.duration) * 100;
597
+ this.elements.progress.style.width = `${progress}%`;
598
+
599
+ this.elements.currentTime.textContent = this.formatTime(this.audio.currentTime);
600
+ }
601
+ }
602
+
603
+ updateDuration() {
604
+ if (!isNaN(this.audio.duration)) {
605
+ this.elements.duration.textContent = this.formatTime(this.audio.duration);
606
+ }
607
+ }
608
+
609
+ formatTime(seconds) {
610
+ const mins = Math.floor(seconds / 60);
611
+ const secs = Math.floor(seconds % 60);
612
+ return `${mins}:${secs.toString().padStart(2, '0')}`;
613
+ }
614
+
615
+ seek(e) {
616
+ if (!isNaN(this.audio.duration)) {
617
+ const rect = this.elements.progressBar.getBoundingClientRect();
618
+ const pos = (e.clientX - rect.left) / rect.width;
619
+ this.audio.currentTime = pos * this.audio.duration;
620
+ }
621
+ }
622
+ }
623
+
624
+ // Initialize the player when the page loads
625
+ document.addEventListener('DOMContentLoaded', () => {
626
+ window.player = new PlaylistPlayer();
627
+ });
628
+ </script>
629
+ </body>
630
+ </html>