MySafeCode commited on
Commit
db22656
·
verified ·
1 Parent(s): 0b60ab8

Create index.html

Browse files
Files changed (1) hide show
  1. index.html +712 -0
index.html ADDED
@@ -0,0 +1,712 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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) => {
408
+ console.error('Audio error:', e);
409
+ this.elements.status.textContent = 'Error loading audio. Trying next track...';
410
+ setTimeout(() => this.nextTrack(), 1000);
411
+ });
412
+ }
413
+
414
+ async loadPlaylist() {
415
+ try {
416
+ this.elements.status.innerHTML = '<span class="loading"></span> Loading playlist...';
417
+
418
+ // Load database.json
419
+ const response = await fetch('https://1hit.no/gen/database.json');
420
+ const database = await response.json();
421
+
422
+ this.playlist = [];
423
+ this.originalPlaylist = [];
424
+
425
+ // Process each track in database
426
+ const trackPromises = Object.entries(database).map(async ([taskId, trackData]) => {
427
+ try {
428
+ // Try to load callback JSON for this track
429
+ const callbackResponse = await fetch(`https://1hit.no/gen/callbacks/${taskId}.json`);
430
+ const callbackData = await callbackResponse.json();
431
+
432
+ // Construct audio URL based on the pattern you showed
433
+ // The taskid appears to be the filename without the _0.mp3 suffix
434
+ const audioFilename = taskId + '_0.mp3';
435
+ const audioUrl = `https://1hit.no/gen/audio/mp3/${audioFilename}`;
436
+
437
+ // Alternative path if the above doesn't work
438
+ const audioUrlAlt = `https://1hit.no/gen/audio/mp3s/${audioFilename}`;
439
+
440
+ const track = {
441
+ id: taskId,
442
+ audioUrl: audioUrl,
443
+ audioUrlAlt: audioUrlAlt,
444
+ title: callbackData.title || 'Unknown Title',
445
+ artist: callbackData.artist || 'Unknown Artist',
446
+ image: callbackData.image || null,
447
+ lyrics: callbackData.lyrics || null,
448
+ duration: callbackData.duration || 0
449
+ };
450
+
451
+ return track;
452
+
453
+ } catch (error) {
454
+ console.warn(`Failed to load metadata for track ${taskId}:`, error);
455
+
456
+ // Create a basic track even if callback fails
457
+ const audioFilename = taskId + '_0.mp3';
458
+ const audioUrl = `https://1hit.no/gen/audio/mp3/${audioFilename}`;
459
+ const audioUrlAlt = `https://1hit.no/gen/audio/mp3s/${audioFilename}`;
460
+
461
+ return {
462
+ id: taskId,
463
+ audioUrl: audioUrl,
464
+ audioUrlAlt: audioUrlAlt,
465
+ title: `Track ${taskId.substring(0, 8)}...`,
466
+ artist: 'Unknown Artist',
467
+ image: null,
468
+ lyrics: null,
469
+ duration: 0
470
+ };
471
+ }
472
+ });
473
+
474
+ // Wait for all track metadata to load
475
+ const tracks = await Promise.all(trackPromises);
476
+ this.playlist = tracks.filter(track => track !== null);
477
+ this.originalPlaylist = [...this.playlist];
478
+
479
+ this.renderPlaylist();
480
+ this.elements.trackCount.textContent = this.playlist.length;
481
+ this.elements.status.textContent = `Loaded ${this.playlist.length} tracks`;
482
+
483
+ if (this.playlist.length > 0) {
484
+ this.selectTrack(0);
485
+ }
486
+
487
+ } catch (error) {
488
+ console.error('Failed to load playlist:', error);
489
+ this.elements.status.textContent = 'Error loading playlist. Make sure CORS is enabled on the server.';
490
+ }
491
+ }
492
+
493
+ renderPlaylist() {
494
+ this.elements.playlist.innerHTML = '';
495
+
496
+ this.playlist.forEach((track, index) => {
497
+ const item = document.createElement('div');
498
+ item.className = `playlist-item ${index === this.currentTrackIndex ? 'active' : ''}`;
499
+ item.innerHTML = `
500
+ <img src="${track.image || 'data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iNTAiIGhlaWdodD0iNTAiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyI+PHJlY3Qgd2lkdGg9IjUwIiBoZWlnaHQ9IjUwIiBmaWxsPSIjNjY3ZWVhIi8+PHRleHQgeD0iMjUiIHk9IjI1IiBmb250LWZhbWlseT0iQXJpYWwiIGZvbnQtc2l6ZT0iMTAiIGZpbGw9IndoaXRlIiB0ZXh0LWFuY2hvcj0ibWlkZGxlIiBkeT0iLjNlbSI+Tk88L3RleHQ+PC9zdmc+'}"
501
+ alt="${track.title}" class="track-image">
502
+ <div class="track-info">
503
+ <div class="track-title">${track.title}</div>
504
+ <div class="track-details">${track.artist}</div>
505
+ </div>
506
+ `;
507
+
508
+ item.addEventListener('click', () => this.selectTrack(index));
509
+ this.elements.playlist.appendChild(item);
510
+ });
511
+ }
512
+
513
+ async selectTrack(index) {
514
+ if (index < 0 || index >= this.playlist.length) return;
515
+
516
+ this.currentTrackIndex = index;
517
+ const track = this.playlist[index];
518
+
519
+ // Update UI
520
+ this.elements.currentTitle.textContent = track.title;
521
+ this.elements.currentArtist.textContent = track.artist;
522
+ this.elements.currentImage.src = track.image || 'data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iMjAwIiBoZWlnaHQ9IjIwMCIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIj48cmVjdCB3aWR0aD0iMjAwIiBoZWlnaHQ9IjIwMCIgZmlsbD0idXJsKCNncmFkaWVudCkiLz48dGV4dCB4PSIxMDAiIHk9IjEwMCIgZm9udC1mYW1pbHk9IkFyaWFsIiBmb250LXNpemU9IjE0IiBmaWxsPSJ3aGl0ZSIgdGV4dC1hbmNob3I9Im1pZGRsZSIgZHk9Ii4zZW0iPiR7dHJhY2sudGl0bGV9PC90ZXh0PjxsaW5lYXJHcmFkaWVudCBpZD0iZ3JhZGllbnQiPjxzdG9wIG9mZnNldD0iMCIgc3RvcC1jb2xvcj0iIzY2N2VlYSIvPjxzdG9wIG9mZnNldD0iMTAwJSIgc3RvcC1jb2xvcj0iIzc2NGJhMiIvPjwvbGluZWFyR3JhZGllbnQ+PC9zdmc+';
523
+ this.elements.lyricsContent.textContent = track.lyrics || 'No lyrics available for this track.';
524
+
525
+ // Update playlist highlights
526
+ this.renderPlaylist();
527
+
528
+ // Try loading audio from primary URL first, fallback to alternative
529
+ await this.loadAudio(track);
530
+ }
531
+
532
+ async loadAudio(track) {
533
+ this.elements.status.innerHTML = '<span class="loading"></span> Loading audio...';
534
+
535
+ try {
536
+ // Try primary URL first
537
+ this.audio.src = track.audioUrl;
538
+ this.audio.load();
539
+
540
+ // Test if audio loads successfully
541
+ await new Promise((resolve, reject) => {
542
+ this.audio.addEventListener('canplay', resolve);
543
+ this.audio.addEventListener('error', reject);
544
+
545
+ // Timeout after 5 seconds
546
+ setTimeout(() => reject(new Error('Audio load timeout')), 5000);
547
+ });
548
+
549
+ this.elements.status.textContent = `Playing: ${track.title}`;
550
+ this.play();
551
+
552
+ } catch (error) {
553
+ console.warn(`Failed to load audio from primary URL: ${track.audioUrl}`);
554
+
555
+ // Try alternative URL
556
+ try {
557
+ this.audio.src = track.audioUrlAlt;
558
+ this.audio.load();
559
+
560
+ await new Promise((resolve, reject) => {
561
+ this.audio.addEventListener('canplay', resolve);
562
+ this.audio.addEventListener('error', reject);
563
+ setTimeout(() => reject(new Error('Audio load timeout')), 5000);
564
+ });
565
+
566
+ this.elements.status.textContent = `Playing: ${track.title}`;
567
+ this.play();
568
+
569
+ } catch (altError) {
570
+ console.error(`Failed to load audio from alternative URL: ${track.audioUrlAlt}`);
571
+ this.elements.status.textContent = `Failed to load audio for: ${track.title}`;
572
+
573
+ // Try next track after delay
574
+ setTimeout(() => this.nextTrack(), 2000);
575
+ }
576
+ }
577
+ }
578
+
579
+ togglePlay() {
580
+ if (!this.audio.src) return;
581
+
582
+ if (this.isPlaying) {
583
+ this.pause();
584
+ } else {
585
+ this.play();
586
+ }
587
+ }
588
+
589
+ play() {
590
+ if (!this.audio.src && this.playlist.length > 0) {
591
+ this.selectTrack(0);
592
+ }
593
+
594
+ this.audio.play()
595
+ .then(() => {
596
+ this.isPlaying = true;
597
+ this.elements.playPauseBtn.textContent = '⏸';
598
+ })
599
+ .catch(error => {
600
+ console.error('Playback failed:', error);
601
+ this.elements.status.textContent = 'Playback failed. Trying next track...';
602
+ setTimeout(() => this.nextTrack(), 1000);
603
+ });
604
+ }
605
+
606
+ pause() {
607
+ this.audio.pause();
608
+ this.isPlaying = false;
609
+ this.elements.playPauseBtn.textContent = '▶';
610
+ }
611
+
612
+ nextTrack() {
613
+ if (this.playlist.length === 0) return;
614
+
615
+ let nextIndex = this.currentTrackIndex + 1;
616
+
617
+ if (nextIndex >= this.playlist.length) {
618
+ if (this.isRepeating) {
619
+ nextIndex = 0;
620
+ } else {
621
+ this.pause();
622
+ this.elements.status.textContent = 'End of playlist';
623
+ return;
624
+ }
625
+ }
626
+
627
+ this.selectTrack(nextIndex);
628
+ }
629
+
630
+ prevTrack() {
631
+ if (this.playlist.length === 0 || this.currentTrackIndex <= 0) return;
632
+
633
+ this.selectTrack(this.currentTrackIndex - 1);
634
+ }
635
+
636
+ toggleShuffle() {
637
+ this.isShuffled = !this.isShuffled;
638
+
639
+ if (this.isShuffled) {
640
+ // Shuffle the playlist
641
+ const currentTrack = this.playlist[this.currentTrackIndex];
642
+ const shuffled = [...this.playlist];
643
+ for (let i = shuffled.length - 1; i > 0; i--) {
644
+ const j = Math.floor(Math.random() * (i + 1));
645
+ [shuffled[i], shuffled[j]] = [shuffled[j], shuffled[i]];
646
+ }
647
+
648
+ // Keep current track at the same position
649
+ const currentIndex = shuffled.findIndex(track => track.id === currentTrack.id);
650
+ if (currentIndex > 0) {
651
+ [shuffled[0], shuffled[currentIndex]] = [shuffled[currentIndex], shuffled[0]];
652
+ }
653
+
654
+ this.playlist = shuffled;
655
+ this.currentTrackIndex = 0;
656
+ this.elements.shuffleBtn.classList.add('active');
657
+ this.elements.shuffleBtn.textContent = '🔀 Shuffled';
658
+ } else {
659
+ // Restore original order
660
+ const currentTrack = this.playlist[this.currentTrackIndex];
661
+ this.playlist = [...this.originalPlaylist];
662
+ this.currentTrackIndex = this.playlist.findIndex(track => track.id === currentTrack.id);
663
+ this.elements.shuffleBtn.classList.remove('active');
664
+ this.elements.shuffleBtn.textContent = '🔀 Shuffle';
665
+ }
666
+
667
+ this.renderPlaylist();
668
+ }
669
+
670
+ toggleRepeat() {
671
+ this.isRepeating = !this.isRepeating;
672
+ this.elements.repeatBtn.textContent = this.isRepeating ? '🔁 Repeat On' : '🔁 Repeat Off';
673
+ this.elements.repeatBtn.classList.toggle('active', this.isRepeating);
674
+ }
675
+
676
+ updateProgress() {
677
+ if (!isNaN(this.audio.duration)) {
678
+ const progress = (this.audio.currentTime / this.audio.duration) * 100;
679
+ this.elements.progress.style.width = `${progress}%`;
680
+
681
+ this.elements.currentTime.textContent = this.formatTime(this.audio.currentTime);
682
+ }
683
+ }
684
+
685
+ updateDuration() {
686
+ if (!isNaN(this.audio.duration)) {
687
+ this.elements.duration.textContent = this.formatTime(this.audio.duration);
688
+ }
689
+ }
690
+
691
+ formatTime(seconds) {
692
+ const mins = Math.floor(seconds / 60);
693
+ const secs = Math.floor(seconds % 60);
694
+ return `${mins}:${secs.toString().padStart(2, '0')}`;
695
+ }
696
+
697
+ seek(e) {
698
+ if (!isNaN(this.audio.duration)) {
699
+ const rect = this.elements.progressBar.getBoundingClientRect();
700
+ const pos = (e.clientX - rect.left) / rect.width;
701
+ this.audio.currentTime = pos * this.audio.duration;
702
+ }
703
+ }
704
+ }
705
+
706
+ // Initialize the player when the page loads
707
+ document.addEventListener('DOMContentLoaded', () => {
708
+ window.player = new PlaylistPlayer();
709
+ });
710
+ </script>
711
+ </body>
712
+ </html>