xukunCai commited on
Commit
14ddbbc
·
verified ·
1 Parent(s): 1504438

Update index.html

Browse files
Files changed (1) hide show
  1. index.html +208 -1228
index.html CHANGED
@@ -6,764 +6,105 @@
6
  <title>云音乐 - 在线音乐播放器</title>
7
  <link href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.0.0/css/all.min.css" rel="stylesheet">
8
  <style>
9
- * {
10
- margin: 0;
11
- padding: 0;
12
- box-sizing: border-box;
13
- }
14
-
15
- body {
16
- font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'PingFang SC', 'Hiragino Sans GB', 'Microsoft YaHei', sans-serif;
17
- background: #f8f9fa;
18
- color: #333;
19
- overflow-x: hidden;
20
- line-height: 1.6;
21
- }
22
-
23
- /* 顶部导航 */
24
- .navbar {
25
- background: #ffffff;
26
- box-shadow: 0 2px 10px rgba(0, 0, 0, 0.05);
27
- padding: 15px 0;
28
- position: sticky;
29
- top: 0;
30
- z-index: 100;
31
- }
32
-
33
- .nav-container {
34
- max-width: 1400px;
35
- margin: 0 auto;
36
- padding: 0 30px;
37
- display: flex;
38
- align-items: center;
39
- justify-content: space-between;
40
- }
41
-
42
- .logo {
43
- display: flex;
44
- align-items: center;
45
- gap: 12px;
46
- font-size: 24px;
47
- font-weight: bold;
48
- color: #333;
49
- }
50
-
51
- .logo i {
52
- color: #4a90e2;
53
- font-size: 28px;
54
- }
55
-
56
- .search-container {
57
- flex: 1;
58
- max-width: 600px;
59
- margin: 0 40px;
60
- position: relative;
61
- }
62
-
63
- .search-wrapper {
64
- display: flex;
65
- background: #f5f7fa;
66
- border-radius: 25px;
67
- overflow: hidden;
68
- border: 1px solid #e1e5eb;
69
- transition: all 0.3s ease;
70
- }
71
-
72
- .search-wrapper:focus-within {
73
- background: #ffffff;
74
- border-color: #4a90e2;
75
- box-shadow: 0 0 15px rgba(74, 144, 226, 0.2);
76
- }
77
-
78
- .search-input {
79
- flex: 1;
80
- padding: 12px 20px;
81
- background: transparent;
82
- border: none;
83
- color: #333;
84
- font-size: 16px;
85
- outline: none;
86
- }
87
-
88
- .search-input::placeholder {
89
- color: #adb5bd;
90
- }
91
-
92
- .source-select {
93
- background: #f0f3f7;
94
- border: none;
95
- color: #495057;
96
- padding: 12px 15px;
97
- outline: none;
98
- cursor: pointer;
99
- border-left: 1px solid #e1e5eb;
100
- }
101
-
102
- .source-select option {
103
- background: #ffffff;
104
- color: #333;
105
- padding: 8px;
106
- }
107
-
108
- .search-btn {
109
- background: #4a90e2;
110
- border: none;
111
- color: #fff;
112
- padding: 12px 20px;
113
- cursor: pointer;
114
- transition: all 0.3s ease;
115
- }
116
-
117
- .search-btn:hover {
118
- background: #3a7bc8;
119
- }
120
-
121
- /* 主要内容区域 */
122
- .main-container {
123
- max-width: 1200px;
124
- margin: 0 auto;
125
- padding: 20px;
126
- min-height: calc(100vh - 170px);
127
- }
128
-
129
- /* 内容面板通用样式 */
130
- .content-panel {
131
- background: #ffffff;
132
- border-radius: 16px;
133
- box-shadow: 0 4px 20px rgba(0, 0, 0, 0.05);
134
- padding: 25px;
135
- margin-bottom: 20px;
136
- height: 100%;
137
- }
138
-
139
- .panel-title {
140
- font-size: 20px;
141
- font-weight: 600;
142
- margin-bottom: 20px;
143
- color: #333;
144
- display: flex;
145
- align-items: center;
146
- gap: 10px;
147
- }
148
-
149
- .panel-title i {
150
- color: #4a90e2;
151
- }
152
-
153
- /* 搜索结果区域 - 卡片式布局 */
154
- .search-results {
155
- max-height: 500px;
156
- overflow-y: auto;
157
- scrollbar-width: thin;
158
- scrollbar-color: #ced4da transparent;
159
- display: grid;
160
- grid-template-columns: repeat(auto-fill, minmax(100%, 1fr));
161
- gap: 12px;
162
- padding-right: 10px;
163
- }
164
-
165
- .search-results::-webkit-scrollbar {
166
- width: 6px;
167
- }
168
-
169
- .search-results::-webkit-scrollbar-track {
170
- background: transparent;
171
- }
172
-
173
- .search-results::-webkit-scrollbar-thumb {
174
- background: #ced4da;
175
- border-radius: 3px;
176
- }
177
-
178
- .song-card {
179
- display: flex;
180
- align-items: flex-start;
181
- padding: 12px 15px;
182
- border-radius: 12px;
183
- cursor: pointer;
184
- transition: all 0.3s ease;
185
- background: #f8f9fa;
186
- border: 1px solid #e9ecef;
187
- }
188
-
189
- .song-card:hover {
190
- background: #ffffff;
191
- transform: translateY(-2px);
192
- box-shadow: 0 5px 15px rgba(0, 0, 0, 0.05);
193
- }
194
-
195
- .song-card.active {
196
- background: linear-gradient(135deg, rgba(74, 144, 226, 0.1), rgba(74, 144, 226, 0.05));
197
- border: 1px solid rgba(74, 144, 226, 0.3);
198
- }
199
-
200
- .song-index {
201
- width: 32px;
202
- height: 32px;
203
- border-radius: 50%;
204
- background: #e9ecef;
205
- display: flex;
206
- align-items: center;
207
- justify-content: center;
208
- margin-right: 12px;
209
- font-size: 14px;
210
- font-weight: 600;
211
- color: #495057;
212
- flex-shrink: 0;
213
- }
214
-
215
- .song-card.active .song-index {
216
- background: linear-gradient(135deg, #4a90e2, #3a7bc8);
217
- color: #fff;
218
- }
219
-
220
- .song-info {
221
- flex: 1;
222
- min-width: 0;
223
- padding: 2px 0;
224
- }
225
-
226
- .song-name {
227
- font-weight: 600;
228
- margin-bottom: 3px;
229
- font-size: 15px;
230
- color: #333;
231
- /* 允许标题换行 */
232
- white-space: normal;
233
- line-height: 1.4;
234
- }
235
-
236
- .song-artist {
237
- color: #6c757d;
238
- font-size: 13px;
239
- /* 允许艺术家信息换行 */
240
- white-space: normal;
241
- line-height: 1.4;
242
- }
243
-
244
- .song-duration {
245
- color: #adb5bd;
246
- font-size: 13px;
247
- margin-left: 10px;
248
- white-space: nowrap;
249
- align-self: center;
250
- }
251
-
252
- /* 歌曲操作按钮 */
253
- .song-actions {
254
- display: flex;
255
- gap: 8px;
256
- margin-left: 10px;
257
- align-self: center;
258
- }
259
-
260
- .action-btn {
261
- width: 32px;
262
- height: 32px;
263
- border-radius: 50%;
264
- background: #e9ecef;
265
- border: none;
266
- color: #6c757d;
267
- cursor: pointer;
268
- transition: all 0.3s ease;
269
- display: flex;
270
- align-items: center;
271
- justify-content: center;
272
- font-size: 13px;
273
- }
274
-
275
- .action-btn:hover {
276
- background: #dee2e6;
277
- color: #4a90e2;
278
- transform: scale(1.1);
279
- }
280
-
281
- /* 播放器区域 */
282
- .player-panel {
283
- text-align: center;
284
- padding: 30px 25px;
285
- }
286
-
287
- .current-song {
288
- margin-bottom: 30px;
289
- }
290
-
291
- .current-cover-container {
292
- position: relative;
293
- display: inline-block;
294
- margin-bottom: 25px;
295
- }
296
-
297
- .current-cover {
298
- width: 220px;
299
- height: 220px;
300
- border-radius: 50%;
301
- object-fit: cover;
302
- box-shadow: 0 15px 35px rgba(74, 144, 226, 0.2);
303
- transition: all 0.3s ease;
304
- border: 6px solid #f8f9fa;
305
- }
306
-
307
- .current-cover.playing {
308
- animation: rotate 20s linear infinite;
309
- }
310
-
311
- @keyframes rotate {
312
- from { transform: rotate(0deg); }
313
- to { transform: rotate(360deg); }
314
- }
315
-
316
- .current-info h3 {
317
- font-size: 22px;
318
- font-weight: 600;
319
- margin-bottom: 8px;
320
- color: #333;
321
- }
322
-
323
- .current-info p {
324
- color: #6c757d;
325
- font-size: 16px;
326
- }
327
-
328
- /* 播放控制 */
329
- .player-controls {
330
- display: flex;
331
- justify-content: center;
332
- align-items: center;
333
- gap: 25px;
334
- margin-bottom: 30px;
335
- }
336
-
337
- .control-btn {
338
- background: #f1f3f5;
339
- border: none;
340
- border-radius: 50%;
341
- color: #495057;
342
- cursor: pointer;
343
- transition: all 0.3s ease;
344
- display: flex;
345
- align-items: center;
346
- justify-content: center;
347
- }
348
-
349
- .control-btn:hover {
350
- background: #e9ecef;
351
- transform: scale(1.1);
352
- }
353
-
354
- .control-btn.small {
355
- width: 50px;
356
- height: 50px;
357
- font-size: 18px;
358
- }
359
-
360
- .play-btn {
361
- width: 70px;
362
- height: 70px;
363
- font-size: 28px;
364
- background: linear-gradient(135deg, #4a90e2, #3a7bc8);
365
- color: white;
366
- box-shadow: 0 8px 25px rgba(74, 144, 226, 0.3);
367
- }
368
-
369
- .play-btn:hover {
370
- background: linear-gradient(135deg, #3a7bc8, #2d6bc1);
371
- box-shadow: 0 12px 35px rgba(74, 144, 226, 0.4);
372
- }
373
-
374
- /* 进度条 */
375
- .progress-container {
376
- margin-bottom: 30px;
377
- padding: 0 20px;
378
- }
379
-
380
- .progress-bar {
381
- width: 100%;
382
- height: 6px;
383
- background: #e9ecef;
384
- border-radius: 3px;
385
- cursor: pointer;
386
- margin-bottom: 10px;
387
- position: relative;
388
- }
389
-
390
- .progress-fill {
391
- height: 100%;
392
- background: linear-gradient(90deg, #4a90e2, #6aa8e6);
393
- border-radius: 3px;
394
- width: 0%;
395
- transition: width 0.1s ease;
396
- position: relative;
397
- }
398
-
399
- .progress-fill::after {
400
- content: '';
401
- position: absolute;
402
- right: -2px;
403
- top: 50%;
404
- transform: translateY(-50%);
405
- width: 14px;
406
- height: 14px;
407
- background: #4a90e2;
408
- border-radius: 50%;
409
- box-shadow: 0 2px 8px rgba(74, 144, 226, 0.5);
410
- }
411
-
412
- .time-info {
413
- display: flex;
414
- justify-content: space-between;
415
- font-size: 14px;
416
- color: #6c757d;
417
- }
418
-
419
- /* 音量控制 */
420
- .volume-container {
421
- display: flex;
422
- align-items: center;
423
- gap: 12px;
424
- margin-bottom: 30px;
425
- justify-content: center;
426
- }
427
-
428
- .volume-icon {
429
- color: #6c757d;
430
- font-size: 18px;
431
- }
432
-
433
- .volume-slider {
434
- width: 150px;
435
- height: 4px;
436
- background: #e9ecef;
437
- border-radius: 2px;
438
- outline: none;
439
- cursor: pointer;
440
- -webkit-appearance: none;
441
- }
442
-
443
- .volume-slider::-webkit-slider-thumb {
444
- -webkit-appearance: none;
445
- width: 14px;
446
- height: 14px;
447
- background: #4a90e2;
448
- border-radius: 50%;
449
- cursor: pointer;
450
- }
451
-
452
- /* 音质选择 */
453
- .quality-container {
454
- display: flex;
455
- align-items: center;
456
- justify-content: space-between;
457
- margin-bottom: 25px;
458
- padding: 15px 20px;
459
- background: #f8f9fa;
460
- border-radius: 12px;
461
- border: 1px solid #e9ecef;
462
- }
463
-
464
- .quality-label {
465
- display: flex;
466
- align-items: center;
467
- gap: 8px;
468
- color: #495057;
469
- font-size: 14px;
470
- }
471
-
472
- .quality-select {
473
- background: #ffffff;
474
- border: 1px solid #ced4da;
475
- border-radius: 8px;
476
- color: #333;
477
- padding: 8px 12px;
478
- outline: none;
479
- cursor: pointer;
480
- font-size: 14px;
481
- }
482
-
483
- .quality-select option {
484
- background: #ffffff;
485
- color: #333;
486
- padding: 8px;
487
- }
488
-
489
- /* 下载区域 */
490
- .download-container {
491
- display: grid;
492
- grid-template-columns: 1fr 1fr;
493
- gap: 15px;
494
- margin-bottom: 20px;
495
- }
496
-
497
- .download-btn {
498
- display: flex;
499
- align-items: center;
500
- justify-content: center;
501
- gap: 8px;
502
- padding: 12px 15px;
503
- background: #f8f9fa;
504
- border: 1px solid #e9ecef;
505
- border-radius: 12px;
506
- color: #495057;
507
- cursor: pointer;
508
- transition: all 0.3s ease;
509
- font-size: 14px;
510
- }
511
-
512
- .download-btn:hover:not(:disabled) {
513
- background: #eef2f7;
514
- border-color: #4a90e2;
515
- color: #4a90e2;
516
- }
517
-
518
- .download-btn:disabled {
519
- opacity: 0.5;
520
- cursor: not-allowed;
521
- }
522
-
523
- /* 歌词区域 */
524
- .lyrics-container {
525
- max-height: 500px;
526
- overflow-y: auto;
527
- scrollbar-width: thin;
528
- scrollbar-color: #ced4da transparent;
529
- padding-right: 10px;
530
- position: relative;
531
- }
532
-
533
- .lyrics-container::-webkit-scrollbar {
534
- width: 6px;
535
- }
536
-
537
- .lyrics-container::-webkit-scrollbar-track {
538
- background: transparent;
539
- }
540
-
541
- .lyrics-container::-webkit-scrollbar-thumb {
542
- background: #ced4da;
543
- border-radius: 3px;
544
- }
545
-
546
- .lyric-line {
547
- padding: 8px 15px;
548
- transition: all 0.3s ease;
549
- cursor: pointer;
550
- border-radius: 8px;
551
- margin-bottom: 6px;
552
- color: #6c757d;
553
- line-height: 1.6;
554
- text-align: center;
555
- font-size: 15px;
556
- }
557
-
558
- .lyric-line:hover {
559
- background: #f8f9fa;
560
- color: #4a90e2;
561
- }
562
-
563
- .lyric-line.active {
564
- color: #4a90e2;
565
- font-weight: 600;
566
- background: rgba(74, 144, 226, 0.08);
567
- transform: scale(1.02);
568
- }
569
-
570
- /* 加载和错误状态 */
571
- .loading, .error, .empty-state {
572
- text-align: center;
573
- padding: 60px 20px;
574
- color: #6c757d;
575
- }
576
-
577
- .loading i, .error i, .empty-state i {
578
- font-size: 48px;
579
- margin-bottom: 15px;
580
- display: block;
581
- color: #adb5bd;
582
- }
583
-
584
- .loading i {
585
- animation: spin 1s linear infinite;
586
- color: #4a90e2;
587
- }
588
-
589
- @keyframes spin {
590
- from { transform: rotate(0deg); }
591
- to { transform: rotate(360deg); }
592
- }
593
-
594
- .error i {
595
- color: #e74c3c;
596
- }
597
-
598
- /* 移动端底部导航 */
599
- .mobile-nav {
600
- display: none;
601
- position: fixed;
602
- bottom: 0;
603
- left: 0;
604
- width: 100%;
605
- background: #ffffff;
606
- box-shadow: 0 -2px 15px rgba(0, 0, 0, 0.05);
607
- z-index: 99;
608
- }
609
-
610
- .mobile-nav-items {
611
- display: flex;
612
- justify-content: space-around;
613
- }
614
-
615
- .mobile-nav-item {
616
- flex: 1;
617
- text-align: center;
618
- padding: 15px 0;
619
- color: #6c757d;
620
- text-decoration: none;
621
- font-size: 12px;
622
- transition: all 0.3s ease;
623
- }
624
-
625
- .mobile-nav-item.active {
626
- color: #4a90e2;
627
- }
628
-
629
- .mobile-nav-item i {
630
- font-size: 20px;
631
- margin-bottom: 5px;
632
- display: block;
633
- }
634
-
635
- /* 内容视图容器 */
636
- .view-container {
637
- display: block;
638
- }
639
-
640
- /* 响应式设计 */
641
- @media (min-width: 1200px) {
642
- .desktop-layout {
643
- display: grid;
644
- grid-template-columns: 350px 450px 350px;
645
- gap: 25px;
646
- }
647
-
648
- .mobile-nav {
649
- display: none !important;
650
- }
651
- }
652
-
653
- @media (max-width: 1199px) {
654
- .mobile-nav {
655
- display: block;
656
- }
657
-
658
- .main-container {
659
- padding-bottom: 80px;
660
- }
661
-
662
- .desktop-layout {
663
- display: block;
664
- }
665
-
666
- .view-container {
667
- display: none;
668
- }
669
-
670
- .view-container.active {
671
- display: block;
672
- }
673
-
674
- /* 移动端提高信息密度 */
675
- .song-name {
676
- font-size: 14px;
677
- line-height: 1.4;
678
- }
679
-
680
- .song-artist {
681
- font-size: 13px;
682
- line-height: 1.4;
683
- }
684
-
685
- .song-card {
686
- padding: 10px 12px;
687
- gap: 10px;
688
- }
689
-
690
- .song-index {
691
- width: 28px;
692
- height: 28px;
693
- font-size: 13px;
694
- }
695
-
696
- .action-btn {
697
- width: 28px;
698
- height: 28px;
699
- font-size: 12px;
700
- }
701
- }
702
-
703
- @media (max-width: 768px) {
704
- .nav-container {
705
- flex-direction: column;
706
- gap: 15px;
707
- padding: 0 15px;
708
- }
709
-
710
- .search-container {
711
- margin: 0;
712
- max-width: none;
713
- width: 100%;
714
- }
715
-
716
- .current-cover {
717
- width: 180px;
718
- height: 180px;
719
- }
720
-
721
- .player-controls {
722
- gap: 15px;
723
- }
724
-
725
- .content-panel {
726
- padding: 15px 12px;
727
- }
728
-
729
- .panel-title {
730
- font-size: 17px;
731
- margin-bottom: 15px;
732
- }
733
-
734
- .search-results {
735
- gap: 10px;
736
- }
737
- }
738
-
739
- /* 自定义滚动条样式 */
740
- ::-webkit-scrollbar {
741
- width: 8px;
742
- }
743
-
744
- ::-webkit-scrollbar-track {
745
- background: #f8f9fa;
746
- border-radius: 4px;
747
- }
748
-
749
- ::-webkit-scrollbar-thumb {
750
- background: #ced4da;
751
- border-radius: 4px;
752
- }
753
-
754
- ::-webkit-scrollbar-thumb:hover {
755
- background: #adb5bd;
756
- }
757
  </style>
758
  </head>
759
  <body>
 
760
  <nav class="navbar">
761
  <div class="nav-container">
762
- <div class="logo">
763
- <i class="fas fa-music"></i>
764
- <span>云音乐</span>
765
- </div>
766
-
767
  <div class="search-container">
768
  <div class="search-wrapper">
769
  <input type="text" class="search-input" placeholder="搜索音乐、歌手、专辑..." id="searchInput">
@@ -782,70 +123,38 @@
782
  <option value="qobuz">Qobuz</option>
783
  <option value="ximalaya">喜马拉雅</option>
784
  </select>
785
- <button class="search-btn" onclick="searchMusic()">
786
- <i class="fas fa-search"></i>
787
- </button>
788
  </div>
789
  </div>
790
  </div>
791
  </nav>
792
-
793
  <div class="main-container">
794
  <div class="desktop-layout">
795
  <div class="view-container" id="searchView">
796
  <div class="content-panel">
797
- <h2 class="panel-title">
798
- <i class="fas fa-search"></i>
799
- 搜索结果
800
- </h2>
801
  <div class="search-results" id="searchResults">
802
- <div class="empty-state">
803
- <i class="fas fa-music"></i>
804
- <div>在上方搜索框输入关键词开始搜索音乐</div>
805
- </div>
806
  </div>
807
  </div>
808
  </div>
809
-
810
  <div class="view-container" id="playerView">
811
  <div class="content-panel player-panel">
812
  <div class="current-song">
813
- <div class="current-cover-container">
814
- <img class="current-cover" id="currentCover" src="data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iMjIwIiBoZWlnaHQ9IjIyMCIgdmlld0JveD0iMCAwIDIyMCAyMjAiIGZpbGw9Im5vbmUiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyI+CjxyZWN0IHdpZHRoPSIyMjAiIGhlaWdodD0iMjIwIiBmaWxsPSIjZmZmZmZmIi8+CjxjaXJjbGUgY3g9IjExMCIgY3k9IjExMCIgcj0iODAiIGZpbGw9IiNmOGY5ZmEiLz4KPHBhdGggZD0iTTEwNSAxNzVWODVIMTE1VjE3NVoiIGZpbGw9IiM0YTkwZTIiLz4KPC9zdmc+" alt="专辑封面">
815
- </div>
816
- <div class="current-info">
817
- <h3 id="currentTitle">未选择歌曲</h3>
818
- <p id="currentArtist">请搜索并选择要播放的歌曲</p>
819
- </div>
820
  </div>
821
-
822
  <div class="player-controls">
823
- <button class="control-btn small" onclick="previousSong()">
824
- <i class="fas fa-step-backward"></i>
825
- </button>
826
- <button class="control-btn play-btn" id="playBtn" onclick="togglePlay()">
827
- <i class="fas fa-play"></i>
828
- </button>
829
- <button class="control-btn small" onclick="nextSong()">
830
- <i class="fas fa-step-forward"></i>
831
- </button>
832
  </div>
833
-
834
  <div class="progress-container">
835
- <div class="progress-bar" onclick="seekTo(event)">
836
- <div class="progress-fill" id="progressFill"></div>
837
- </div>
838
- <div class="time-info">
839
- <span id="currentTime">0:00</span>
840
- <span id="totalTime">0:00</span>
841
- </div>
842
  </div>
843
-
844
  <div class="quality-container">
845
- <div class="quality-label">
846
- <i class="fas fa-music"></i>
847
- <span>音质</span>
848
- </div>
849
  <select class="quality-select" id="qualitySelect">
850
  <option value="128">标准 128K</option>
851
  <option value="192">较高 192K</option>
@@ -854,68 +163,41 @@
854
  <option value="999">Hi-Res</option>
855
  </select>
856
  </div>
857
-
858
  <div class="volume-container">
859
  <i class="fas fa-volume-up volume-icon"></i>
860
  <input type="range" class="volume-slider" id="volumeSlider" min="0" max="100" value="80" onchange="setVolume(this.value)">
861
  </div>
862
-
863
  <div class="download-container">
864
- <button class="download-btn" onclick="downloadCurrentSong()" id="downloadSongBtn" disabled>
865
- <i class="fas fa-download"></i>
866
- <span>下载音乐</span>
867
- </button>
868
- <button class="download-btn" onclick="downloadCurrentLyric()" id="downloadLyricBtn" disabled>
869
- <i class="fas fa-file-text"></i>
870
- <span>下载歌词</span>
871
- </button>
872
  </div>
873
-
874
  <audio id="audioPlayer" preload="metadata"></audio>
875
  </div>
876
  </div>
877
-
878
  <div class="view-container" id="lyricsView">
879
  <div class="content-panel">
880
- <h2 class="panel-title">
881
- <i class="fas fa-align-left"></i>
882
- 歌词
883
- </h2>
884
- <div class="lyrics-container" id="lyricsContainer">
885
- <div class="lyric-line">暂无歌词</div>
886
- </div>
887
  </div>
888
  </div>
889
  </div>
890
  </div>
891
-
892
  <div class="mobile-nav">
893
  <div class="mobile-nav-items">
894
- <a href="#searchView" class="mobile-nav-item active" onclick="switchView('searchView', this)">
895
- <i class="fas fa-search"></i>
896
- <span>搜索</span>
897
- </a>
898
- <a href="#playerView" class="mobile-nav-item" onclick="switchView('playerView', this)">
899
- <i class="fas fa-play-circle"></i>
900
- <span>播放</span>
901
- </a>
902
- <a href="#lyricsView" class="mobile-nav-item" onclick="switchView('lyricsView', this)">
903
- <i class="fas fa-align-left"></i>
904
- <span>歌词</span>
905
- </a>
906
  </div>
907
  </div>
908
 
909
  <script>
910
- // Dr.Kun: 常量定义
911
  const API_BASE = 'https://music-api.gdstudio.xyz/api.php';
912
- const PLAYER_STATE_KEY = 'cloudMusicPlayerState_v1'; // Dr.Kun: 定义统一的localStorage Key
913
-
914
- // Dr.Kun: 全局状态变量
915
  let currentPlaylist = [];
916
  let currentIndex = -1;
917
- let currentLyrics = [];
918
  let isPlaying = false;
 
919
 
920
  // Dr.Kun: DOM元素缓存
921
  const audioPlayer = document.getElementById('audioPlayer');
@@ -930,7 +212,7 @@
930
  const volumeSlider = document.getElementById('volumeSlider');
931
  const searchInput = document.getElementById('searchInput');
932
 
933
- // Dr.Kun: 新增 - 保存播放器状态到localStorage
934
  function savePlayerState() {
935
  const state = {
936
  playlist: currentPlaylist,
@@ -938,57 +220,62 @@
938
  volume: audioPlayer.volume,
939
  quality: qualitySelect.value,
940
  source: sourceSelect.value,
 
941
  };
942
  try {
943
  localStorage.setItem(PLAYER_STATE_KEY, JSON.stringify(state));
944
  } catch (e) {
945
  console.error("保存状态失败:", e);
946
- showNotification("无法保存播放状态,可能是存储已满", "error");
947
  }
948
  }
949
 
950
- // Dr.Kun: 新增 - 从localStorage加载播放器状态
951
  async function loadPlayerState() {
952
  try {
953
  const savedState = localStorage.getItem(PLAYER_STATE_KEY);
954
- if (savedState) {
955
- const state = JSON.parse(savedState);
956
-
957
- // 恢复状态
958
- currentPlaylist = state.playlist || [];
959
- currentIndex = state.index || -1;
960
-
961
- // 恢复UI
962
- qualitySelect.value = state.quality || '320';
963
- sourceSelect.value = state.source || 'netease';
964
-
965
- // 恢复音量 (注意: setVolume接受0-100的值)
966
- const volumeValue = (state.volume !== undefined ? state.volume : 0.8) * 100;
967
- volumeSlider.value = volumeValue;
968
- setVolume(volumeValue);
969
-
970
- // 如果有播放列表,则渲染
971
- if (currentPlaylist.length > 0) {
972
- displaySearchResults(currentPlaylist);
973
- }
974
-
975
- // 如果有当前播放的歌曲,则更新播放器信息(但不自动播放)
976
- if (currentIndex > -1 && currentPlaylist[currentIndex]) {
977
- await updateCurrentSongInfo(currentPlaylist[currentIndex]);
978
- updateActiveItem();
979
- document.getElementById('downloadSongBtn').disabled = false;
980
- document.getElementById('downloadLyricBtn').disabled = false;
 
 
 
981
  }
 
 
 
982
  }
983
  } catch (e) {
984
- console.error("加载状态失败, 可能数据已损坏:", e);
985
- localStorage.removeItem(PLAYER_STATE_KEY); // Dr.Kun: 如果数据损坏��则清除,避免下次继续出错
986
  }
987
  }
988
 
989
  // Dr.Kun: 页面加载时执行的初始化函数
990
  async function initializeApp() {
991
- // 视图初始化
992
  if (window.innerWidth >= 1200) {
993
  document.getElementById('searchView').classList.add('active');
994
  document.getElementById('playerView').classList.add('active');
@@ -997,97 +284,39 @@
997
  switchView('searchView', document.querySelectorAll('.mobile-nav-item')[0]);
998
  }
999
 
1000
- // Dr.Kun: 关键步骤 - 加载上次保存的状态
1001
  await loadPlayerState();
1002
 
1003
- // Dr.Kun: 为需要保存状态的控件添加事件监听
1004
  qualitySelect.onchange = savePlayerState;
1005
  sourceSelect.onchange = savePlayerState;
1006
- }
1007
 
1008
- // 切换视图
1009
- function switchView(viewId, navItem) {
1010
- document.querySelectorAll('.view-container').forEach(view => view.classList.remove('active'));
1011
- document.getElementById(viewId).classList.add('active');
1012
- if (navItem) {
1013
- document.querySelectorAll('.mobile-nav-item').forEach(item => item.classList.remove('active'));
1014
- navItem.classList.add('active');
1015
- }
1016
  }
1017
 
1018
- // 搜索音乐
1019
- async function searchMusic() {
1020
- const keyword = searchInput.value.trim();
1021
- const source = sourceSelect.value;
1022
- if (!keyword) {
1023
- showNotification('请输入搜索关键词', 'warning');
1024
- return;
1025
- }
1026
-
1027
- const resultsContainer = document.getElementById('searchResults');
1028
- resultsContainer.innerHTML = `<div class="loading"><i class="fas fa-spinner"></i><div>正在搜索音乐...</div></div>`;
1029
-
1030
- try {
1031
- const response = await fetch(`${API_BASE}?types=search&source=${source}&name=${encodeURIComponent(keyword)}&count=30`);
1032
- const data = await response.json();
1033
- if (data && data.length > 0) {
1034
- currentPlaylist = data;
1035
- currentIndex = -1; // Dr.Kun: 新的搜索重置当前播放索引
1036
- displaySearchResults(data);
1037
- savePlayerState(); // Dr.Kun: 搜索成功后保存播放列表状态
1038
- } else {
1039
- resultsContainer.innerHTML = `<div class="error"><i class="fas fa-exclamation-triangle"></i><div>未找到相关歌曲</div></div>`;
1040
- }
1041
- } catch (error) {
1042
- console.error('搜索失败:', error);
1043
- resultsContainer.innerHTML = `<div class="error"><i class="fas fa-wifi"></i><div>网络连接失败,请重试</div></div>`;
1044
- }
1045
- }
1046
 
1047
- async function getAlbumCoverUrl(song, size = 300) {
1048
- if (!song || !song.pic_id) {
1049
- return 'data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iMjIwIiBoZWlnaHQ9IjIyMCIgdmlld0JveD0iMCAwIDIyMCAyMjAiIGZpbGw9Im5vbmUiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyI+CjxyZWN0IHdpZHRoPSIyMjAiIGhlaWdodD0iMjIwIiBmaWxsPSIjZmZmZmZmIi8+CjxjaXJjbGUgY3g9IjExMCIgY3k9IjExMCIgcj0iODAiIGZpbGw9IiNmOGY5ZmEiLz4KPHBhdGggZD0iTTEwNSAxNzVWODVIMTE1VjE3NVoiIGZpbGw9IiM0YTkwZTIiLz4KPC9zdmc+';
1050
- }
1051
- try {
1052
- const response = await fetch(`${API_BASE}?types=pic&source=${song.source}&id=${song.pic_id}&size=${size}`);
1053
- const data = await response.json();
1054
- return (data && data.url) ? data.url : 'data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iMjIwIiBoZWlnaHQ9IjIyMCIgdmlld0JveD0iMCAwIDIyMCAyMjAiIGZpbGw9Im5vbmUiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyI+CjxyZWN0IHdpZHRoPSIyMjAiIGhlaWdodD0iMjIwIiBmaWxsPSIjZmZmZmZmIi8+CjxjaXJjbGUgY3g9IjExMCIgY3k9IjExMCIgcj0iODAiIGZpbGw9IiNmOGY5ZmEiLz4KPHBhdGggZD0iTTEwNSAxNzVWODVIMTE1VjE3NVoiIGZpbGw9IiM0YTkwZTIiLz4KPC9zdmc+';
1055
- } catch (error) {
1056
- console.error('获取专辑图失败:', error);
1057
- return 'data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iMjIwIiBoZWlnaHQ9IjIyMCIgdmlld0JveD0iMCAwIDIyMCAyMjAiIGZpbGw9Im5vbmUiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyI+CjxyZWN0IHdpZHRoPSIyMjAiIGhlaWdodD0iMjIwIiBmaWxsPSIjZmZmZmZmIi8+CjxjaXJjbGUgY3g9IjExMCIgY3k9IjExMCIgcj0iODAiIGZpbGw9IiNmOGY5ZmEiLz4KPHBhdGggZD0iTTEwNSAxNzVWODVIMTE1VjE3NVoiIGZpbGw9IiM0YTkwZTIiLz4KPC9zdmc+';
1058
  }
1059
  }
1060
-
1061
- function displaySearchResults(songs) {
1062
- const resultsContainer = document.getElementById('searchResults');
1063
- resultsContainer.innerHTML = '';
1064
- songs.forEach((song, index) => {
1065
- const songCard = document.createElement('div');
1066
- songCard.className = 'song-card';
1067
- songCard.onclick = () => playSong(index);
1068
- const artistText = Array.isArray(song.artist) ? song.artist.join(' / ') : song.artist;
1069
- songCard.innerHTML = `
1070
- <div class="song-index">${(index + 1).toString().padStart(2, '0')}</div>
1071
- <div class="song-info">
1072
- <div class="song-name">${song.name}</div>
1073
- <div class="song-artist">${artistText} ${song.album ? '· ' + song.album : ''}</div>
1074
- </div>
1075
- <div class="song-actions">
1076
- <button class="action-btn" onclick="event.stopPropagation(); downloadSong(${index})" title="下载音乐"><i class="fas fa-download"></i></button>
1077
- <button class="action-btn" onclick="event.stopPropagation(); downloadLyric(${index})" title="下载歌词"><i class="fas fa-file-text"></i></button>
1078
- </div>`;
1079
- resultsContainer.appendChild(songCard);
1080
- });
1081
- }
1082
-
1083
  async function playSong(index) {
1084
  if (index < 0 || index >= currentPlaylist.length) return;
1085
  currentIndex = index;
 
1086
  const song = currentPlaylist[index];
1087
  await updateCurrentSongInfo(song);
1088
  updateActiveItem();
1089
- savePlayerState(); // Dr.Kun: 切换歌曲时保存状态
1090
-
1091
  try {
1092
  showNotification('正在加载音乐...', 'info');
1093
  const quality = qualitySelect.value;
@@ -1104,17 +333,13 @@
1104
  const playPromise = audioPlayer.play();
1105
  if (playPromise !== undefined) {
1106
  playPromise.then(() => {
1107
- isPlaying = true;
1108
- updatePlayButton();
1109
- currentCover.classList.add('playing');
1110
  showNotification(`开始播放: ${song.name}`, 'success');
1111
  }).catch(error => {
1112
- console.error('播放失败:', error);
1113
  showNotification('自动播放失败,请手动点击播放', 'warning');
1114
  });
1115
  }
1116
  } else {
1117
- showNotification('无法获取音乐链接,请尝试其他歌曲或更换音质', 'error');
1118
  }
1119
  } catch (error) {
1120
  console.error('播放失败:', error);
@@ -1122,294 +347,49 @@
1122
  }
1123
  }
1124
 
1125
- function getQualityText(br) {
1126
- const qualityMap = { '128': '标准', '192': '较高', '320': '高品质', '740': '无损FLAC', '999': 'Hi-Res' };
1127
- return qualityMap[br] || `${br}K`;
1128
- }
1129
-
1130
- async function downloadSong(index) {
1131
- const song = currentPlaylist[index];
1132
- const quality = qualitySelect.value;
1133
- try {
1134
- showNotification('正在获取下载链接...', 'info');
1135
- const response = await fetch(`${API_BASE}?types=url&source=${song.source}&id=${song.id}&br=${quality}`);
1136
- const data = await response.json();
1137
- if (data && data.url) {
1138
- const link = document.createElement('a');
1139
- link.href = data.url;
1140
- link.download = `${song.name} - ${Array.isArray(song.artist) ? song.artist.join(', ') : song.artist}.mp3`;
1141
- link.target = '_blank';
1142
- document.body.appendChild(link);
1143
- link.click();
1144
- document.body.removeChild(link);
1145
- showNotification('开始下载音乐文件', 'success');
1146
- } else {
1147
- showNotification('无法获取下载链接', 'error');
1148
- }
1149
- } catch (error) {
1150
- console.error('下载失败:', error);
1151
- showNotification('下载失败,请稍后重试', 'error');
1152
- }
1153
- }
1154
-
1155
- async function downloadLyric(index) {
1156
- const song = currentPlaylist[index];
1157
- try {
1158
- showNotification('正在获取歌词...', 'info');
1159
- const response = await fetch(`${API_BASE}?types=lyric&source=${song.source}&id=${song.lyric_id || song.id}`);
1160
- const data = await response.json();
1161
- if (data && data.lyric) {
1162
- let lyricContent = `歌曲:${song.name}\r\n歌手:${Array.isArray(song.artist) ? song.artist.join(', ') : song.artist}\r\n专辑:${song.album}\r\n\r\n${data.lyric}`;
1163
- if (data.tlyric) lyricContent += `\r\n\r\n=== 翻译 ===\r\n${data.tlyric}`;
1164
- const blob = new Blob([lyricContent], { type: 'text/plain;charset=utf-8' });
1165
- const url = URL.createObjectURL(blob);
1166
- const link = document.createElement('a');
1167
- link.href = url;
1168
- link.download = `${song.name} - ${Array.isArray(song.artist) ? song.artist.join(', ') : song.artist}.lrc`;
1169
- document.body.appendChild(link);
1170
- link.click();
1171
- document.body.removeChild(link);
1172
- URL.revokeObjectURL(url);
1173
- showNotification('歌词下载完成', 'success');
1174
- } else {
1175
- showNotification('该歌曲暂无歌词', 'warning');
1176
- }
1177
- } catch (error) {
1178
- console.error('下载歌词失败:', error);
1179
- showNotification('下载歌词失败,请稍后重试', 'error');
1180
- }
1181
- }
1182
-
1183
- async function downloadCurrentSong() {
1184
- if (currentIndex === -1) { showNotification('请先选择歌曲', 'warning'); return; }
1185
- await downloadSong(currentIndex);
1186
- }
1187
-
1188
- async function downloadCurrentLyric() {
1189
- if (currentIndex === -1) { showNotification('请先选择歌曲', 'warning'); return; }
1190
- await downloadLyric(currentIndex);
1191
- }
1192
-
1193
- async function updateCurrentSongInfo(song) {
1194
- document.getElementById('currentTitle').textContent = song.name;
1195
- const artistText = Array.isArray(song.artist) ? song.artist.join(' / ') : song.artist;
1196
- document.getElementById('currentArtist').textContent = `${artistText}${song.album ? ' · ' + song.album : ''}`;
1197
- currentCover.src = await getAlbumCoverUrl(song, 500);
1198
- }
1199
-
1200
- function updateActiveItem() {
1201
- document.querySelectorAll('.song-card').forEach((item, index) => {
1202
- item.classList.toggle('active', index === currentIndex);
1203
- });
1204
- }
1205
-
1206
- function updatePlayButton() {
1207
- playBtn.querySelector('i').className = isPlaying ? 'fas fa-pause' : 'fas fa-play';
1208
- }
1209
-
1210
- function togglePlay() {
1211
- if (!audioPlayer.src) {
1212
- showNotification('请先选择一首歌曲', 'warning');
1213
- return;
1214
- }
1215
- if (isPlaying) {
1216
- audioPlayer.pause();
1217
- currentCover.classList.remove('playing');
1218
- } else {
1219
- audioPlayer.play().catch(e => console.error("播放命令被拒绝", e));
1220
- currentCover.classList.add('playing');
1221
- }
1222
- isPlaying = !isPlaying;
1223
- updatePlayButton();
1224
- }
1225
-
1226
- function setVolume(value) {
1227
- audioPlayer.volume = value / 100;
1228
- const volumeIcon = document.querySelector('.volume-icon');
1229
- if (value == 0) volumeIcon.className = 'fas fa-volume-mute volume-icon';
1230
- else if (value < 50) volumeIcon.className = 'fas fa-volume-down volume-icon';
1231
- else volumeIcon.className = 'fas fa-volume-up volume-icon';
1232
- savePlayerState(); // Dr.Kun: 调整音量后保存状态
1233
- }
1234
-
1235
- function seekTo(event) {
1236
- if(!audioPlayer.duration) return;
1237
- const progressBar = event.currentTarget;
1238
- const rect = progressBar.getBoundingClientRect();
1239
- audioPlayer.currentTime = ((event.clientX - rect.left) / rect.width) * audioPlayer.duration;
1240
- }
1241
-
1242
- async function loadLyrics(song) {
1243
- try {
1244
- const response = await fetch(`${API_BASE}?types=lyric&source=${song.source}&id=${song.lyric_id || song.id}`);
1245
- const data = await response.json();
1246
- currentLyrics = []; // 清空旧歌词
1247
- if (data && data.lyric) {
1248
- currentLyrics = parseLyrics(data.lyric);
1249
- if (data.tlyric) {
1250
- const tLyrics = parseLyrics(data.tlyric);
1251
- mergeLyrics(currentLyrics, tLyrics);
1252
- }
1253
- }
1254
- displayLyrics(currentLyrics);
1255
- } catch (error) {
1256
- console.error('加载歌词失败:', error);
1257
- lyricsContainer.innerHTML = '<div class="lyric-line">加载歌词失败</div>';
1258
- }
1259
- }
1260
-
1261
- function parseLyrics(lyricText) {
1262
- const lines = lyricText.split('\n');
1263
- const lyrics = [];
1264
- const timeRegex = /\[(\d{2}):(\d{2})[.:](\d{2,3})\]/g;
1265
- for (const line of lines) {
1266
- if (!line.trim()) continue;
1267
- let text = line.replace(timeRegex, '').trim();
1268
- if (text) {
1269
- let match;
1270
- timeRegex.lastIndex = 0; // 重置正则索引
1271
- while ((match = timeRegex.exec(line)) !== null) {
1272
- const timeInSeconds = parseInt(match[1]) * 60 + parseInt(match[2]) + parseInt(match[3]) / (match[3].length === 3 ? 1000 : 100);
1273
- lyrics.push({ time: timeInSeconds, text: text });
1274
- }
1275
- }
1276
- }
1277
- return lyrics.sort((a, b) => a.time - b.time);
1278
- }
1279
-
1280
- function mergeLyrics(originalLyrics, translatedLyrics) {
1281
- const translatedMap = new Map(translatedLyrics.map(l => [Math.round(l.time * 10), l.text]));
1282
- originalLyrics.forEach(l => {
1283
- const key = Math.round(l.time * 10);
1284
- const translatedText = translatedMap.get(key);
1285
- if (translatedText) {
1286
- l.text += `<br><span style="color: #adb5bd; font-size: 0.9em;">${translatedText}</span>`;
1287
- }
1288
- });
1289
- }
1290
-
1291
- function displayLyrics(lyrics) {
1292
- lyricsContainer.innerHTML = lyrics.length === 0 ? '<div class="lyric-line">暂无歌词</div>' : '';
1293
- lyrics.forEach(lyric => {
1294
- const line = document.createElement('div');
1295
- line.className = 'lyric-line';
1296
- line.innerHTML = lyric.text; // 使用innerHTML以渲染br标签
1297
- line.setAttribute('data-time', lyric.time);
1298
- line.onclick = () => { if(audioPlayer.duration) audioPlayer.currentTime = lyric.time; };
1299
- lyricsContainer.appendChild(line);
1300
- });
1301
- }
1302
-
1303
- function updateProgress() {
1304
- if (audioPlayer.duration) {
1305
- progressFill.style.width = `${(audioPlayer.currentTime / audioPlayer.duration) * 100}%`;
1306
- currentTimeSpan.textContent = formatTime(audioPlayer.currentTime);
1307
- highlightCurrentLyric();
1308
- }
1309
- }
1310
-
1311
- function formatTime(seconds) {
1312
- const min = Math.floor(seconds / 60);
1313
- const sec = Math.floor(seconds % 60);
1314
- return `${min}:${sec.toString().padStart(2, '0')}`;
1315
- }
1316
-
1317
- function highlightCurrentLyric() {
1318
- const currentTime = audioPlayer.currentTime;
1319
- const lyricLines = document.querySelectorAll('.lyric-line');
1320
- if (lyricLines.length === 0 || lyricsContainer.offsetParent === null) return;
1321
-
1322
- let activeIndex = -1;
1323
- for (let i = lyricLines.length - 1; i >= 0; i--) {
1324
- if (parseFloat(lyricLines[i].getAttribute('data-time')) <= currentTime + 0.2) { // 增加0.2秒的容差
1325
- activeIndex = i;
1326
- break;
1327
- }
1328
- }
1329
-
1330
- lyricLines.forEach((line, index) => {
1331
- if (line.classList.contains('active') !== (index === activeIndex)) {
1332
- line.classList.toggle('active', index === activeIndex);
1333
- }
1334
- });
1335
-
1336
- if (activeIndex > -1) {
1337
- const activeLine = lyricLines[activeIndex];
1338
- const scrollPosition = activeLine.offsetTop - lyricsContainer.clientHeight / 2 + activeLine.clientHeight / 2;
1339
- lyricsContainer.scrollTo({ top: scrollPosition, behavior: 'smooth' });
1340
- }
1341
- }
1342
-
1343
- function showNotification(message, type = 'info') {
1344
- const existing = document.querySelector('.custom-notification');
1345
- if (existing) existing.remove();
1346
- const notification = document.createElement('div');
1347
- notification.className = `custom-notification notification-${type}`;
1348
- const icons = { info: 'info-circle', success: 'check-circle', error: 'exclamation-circle', warning: 'exclamation-triangle' };
1349
- notification.innerHTML = `<i class="fas fa-${icons[type]}"></i><span>${message}</span>`;
1350
- document.body.appendChild(notification);
1351
- Object.assign(notification.style, {
1352
- position: 'fixed', bottom: '20px', right: '20px', padding: '12px 20px', borderRadius: '8px',
1353
- boxShadow: '0 4px 12px rgba(0,0,0,0.15)', display: 'flex', alignItems: 'center', gap: '10px',
1354
- fontSize: '14px', zIndex: '9999', transition: 'all 0.4s cubic-bezier(0.25, 0.8, 0.25, 1)', transform: 'translateY(100px)', opacity: '0'
1355
- });
1356
- const colors = {
1357
- success: { bg: 'rgba(40, 167, 69, 0.9)', color: '#fff' },
1358
- error: { bg: 'rgba(220, 53, 69, 0.9)', color: '#fff' },
1359
- warning: { bg: 'rgba(255, 193, 7, 0.9)', color: '#212529' },
1360
- info: { bg: 'rgba(74, 144, 226, 0.9)', color: '#fff' }
1361
- };
1362
- notification.style.backgroundColor = colors[type].bg;
1363
- notification.style.color = colors[type].color;
1364
- setTimeout(() => {
1365
- notification.style.transform = 'translateY(0)';
1366
- notification.style.opacity = '1';
1367
- }, 50);
1368
- setTimeout(() => {
1369
- notification.style.transform = 'translateY(100px)';
1370
- notification.style.opacity = '0';
1371
- setTimeout(() => notification.remove(), 400);
1372
- }, 3000);
1373
- }
1374
-
1375
- function previousSong() {
1376
- if (currentPlaylist.length === 0) return;
1377
- currentIndex = (currentIndex - 1 + currentPlaylist.length) % currentPlaylist.length;
1378
- playSong(currentIndex);
1379
- }
1380
-
1381
- function nextSong() {
1382
- if (currentPlaylist.length === 0) return;
1383
- currentIndex = (currentIndex + 1) % currentPlaylist.length;
1384
- playSong(currentIndex);
1385
- }
1386
-
1387
- audioPlayer.addEventListener('play', () => { isPlaying = true; updatePlayButton(); currentCover.classList.add('playing'); });
1388
- audioPlayer.addEventListener('pause', () => { isPlaying = false; updatePlayButton(); currentCover.classList.remove('playing'); });
1389
- audioPlayer.addEventListener('ended', nextSong);
1390
  audioPlayer.addEventListener('loadedmetadata', () => {
1391
  totalTimeSpan.textContent = formatTime(audioPlayer.duration);
 
 
 
 
 
 
1392
  });
1393
 
 
 
 
 
1394
  setInterval(updateProgress, 500);
1395
-
1396
- searchInput.addEventListener('keypress', (e) => {
1397
- if (e.key === 'Enter') searchMusic();
1398
- });
1399
-
1400
- document.addEventListener('keydown', (e) => {
1401
- if (['INPUT', 'TEXTAREA'].includes(document.activeElement.tagName)) return;
1402
- const keyMap = {
1403
- ' ': togglePlay,
1404
- 'ArrowRight': () => { if(audioPlayer.duration) audioPlayer.currentTime = Math.min(audioPlayer.currentTime + 5, audioPlayer.duration); },
1405
- 'ArrowLeft': () => { if(audioPlayer.duration) audioPlayer.currentTime = Math.max(audioPlayer.currentTime - 5, 0); },
1406
- 'ArrowUp': () => { const v = Math.min(parseInt(volumeSlider.value) + 5, 100); volumeSlider.value = v; setVolume(v); },
1407
- 'ArrowDown': () => { const v = Math.max(parseInt(volumeSlider.value) - 5, 0); volumeSlider.value = v; setVolume(v); },
1408
- 'n': nextSong, 'N': nextSong, 'p': previousSong, 'P': previousSong
1409
- };
1410
- if (keyMap[e.key]) { e.preventDefault(); keyMap[e.key](); }
1411
- });
 
 
 
 
 
 
 
 
1412
 
 
1413
  initializeApp();
1414
  </script>
1415
  </body>
 
6
  <title>云音乐 - 在线音乐播放器</title>
7
  <link href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.0.0/css/all.min.css" rel="stylesheet">
8
  <style>
9
+ /* CSS样式部分保持不变,此处省略以保持简洁 */
10
+ * { margin: 0; padding: 0; box-sizing: border-box; }
11
+ body { font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'PingFang SC', 'Hiragino Sans GB', 'Microsoft YaHei', sans-serif; background: #f8f9fa; color: #333; overflow-x: hidden; line-height: 1.6; }
12
+ .navbar { background: #ffffff; box-shadow: 0 2px 10px rgba(0, 0, 0, 0.05); padding: 15px 0; position: sticky; top: 0; z-index: 100; }
13
+ .nav-container { max-width: 1400px; margin: 0 auto; padding: 0 30px; display: flex; align-items: center; justify-content: space-between; }
14
+ .logo { display: flex; align-items: center; gap: 12px; font-size: 24px; font-weight: bold; color: #333; }
15
+ .logo i { color: #4a90e2; font-size: 28px; }
16
+ .search-container { flex: 1; max-width: 600px; margin: 0 40px; position: relative; }
17
+ .search-wrapper { display: flex; background: #f5f7fa; border-radius: 25px; overflow: hidden; border: 1px solid #e1e5eb; transition: all 0.3s ease; }
18
+ .search-wrapper:focus-within { background: #ffffff; border-color: #4a90e2; box-shadow: 0 0 15px rgba(74, 144, 226, 0.2); }
19
+ .search-input { flex: 1; padding: 12px 20px; background: transparent; border: none; color: #333; font-size: 16px; outline: none; }
20
+ .search-input::placeholder { color: #adb5bd; }
21
+ .source-select { background: #f0f3f7; border: none; color: #495057; padding: 12px 15px; outline: none; cursor: pointer; border-left: 1px solid #e1e5eb; }
22
+ .source-select option { background: #ffffff; color: #333; padding: 8px; }
23
+ .search-btn { background: #4a90e2; border: none; color: #fff; padding: 12px 20px; cursor: pointer; transition: all 0.3s ease; }
24
+ .search-btn:hover { background: #3a7bc8; }
25
+ .main-container { max-width: 1200px; margin: 0 auto; padding: 20px; min-height: calc(100vh - 170px); }
26
+ .content-panel { background: #ffffff; border-radius: 16px; box-shadow: 0 4px 20px rgba(0, 0, 0, 0.05); padding: 25px; margin-bottom: 20px; height: 100%; }
27
+ .panel-title { font-size: 20px; font-weight: 600; margin-bottom: 20px; color: #333; display: flex; align-items: center; gap: 10px; }
28
+ .panel-title i { color: #4a90e2; }
29
+ .search-results { max-height: 500px; overflow-y: auto; scrollbar-width: thin; scrollbar-color: #ced4da transparent; display: grid; grid-template-columns: repeat(auto-fill, minmax(100%, 1fr)); gap: 12px; padding-right: 10px; }
30
+ .search-results::-webkit-scrollbar { width: 6px; }
31
+ .search-results::-webkit-scrollbar-track { background: transparent; }
32
+ .search-results::-webkit-scrollbar-thumb { background: #ced4da; border-radius: 3px; }
33
+ .song-card { display: flex; align-items: flex-start; padding: 12px 15px; border-radius: 12px; cursor: pointer; transition: all 0.3s ease; background: #f8f9fa; border: 1px solid #e9ecef; }
34
+ .song-card:hover { background: #ffffff; transform: translateY(-2px); box-shadow: 0 5px 15px rgba(0, 0, 0, 0.05); }
35
+ .song-card.active { background: linear-gradient(135deg, rgba(74, 144, 226, 0.1), rgba(74, 144, 226, 0.05)); border: 1px solid rgba(74, 144, 226, 0.3); }
36
+ .song-index { width: 32px; height: 32px; border-radius: 50%; background: #e9ecef; display: flex; align-items: center; justify-content: center; margin-right: 12px; font-size: 14px; font-weight: 600; color: #495057; flex-shrink: 0; }
37
+ .song-card.active .song-index { background: linear-gradient(135deg, #4a90e2, #3a7bc8); color: #fff; }
38
+ .song-info { flex: 1; min-width: 0; padding: 2px 0; }
39
+ .song-name { font-weight: 600; margin-bottom: 3px; font-size: 15px; color: #333; white-space: normal; line-height: 1.4; }
40
+ .song-artist { color: #6c757d; font-size: 13px; white-space: normal; line-height: 1.4; }
41
+ .song-duration { color: #adb5bd; font-size: 13px; margin-left: 10px; white-space: nowrap; align-self: center; }
42
+ .song-actions { display: flex; gap: 8px; margin-left: 10px; align-self: center; }
43
+ .action-btn { width: 32px; height: 32px; border-radius: 50%; background: #e9ecef; border: none; color: #6c757d; cursor: pointer; transition: all 0.3s ease; display: flex; align-items: center; justify-content: center; font-size: 13px; }
44
+ .action-btn:hover { background: #dee2e6; color: #4a90e2; transform: scale(1.1); }
45
+ .player-panel { text-align: center; padding: 30px 25px; }
46
+ .current-song { margin-bottom: 30px; }
47
+ .current-cover-container { position: relative; display: inline-block; margin-bottom: 25px; }
48
+ .current-cover { width: 220px; height: 220px; border-radius: 50%; object-fit: cover; box-shadow: 0 15px 35px rgba(74, 144, 226, 0.2); transition: all 0.3s ease; border: 6px solid #f8f9fa; }
49
+ .current-cover.playing { animation: rotate 20s linear infinite; }
50
+ @keyframes rotate { from { transform: rotate(0deg); } to { transform: rotate(360deg); } }
51
+ .current-info h3 { font-size: 22px; font-weight: 600; margin-bottom: 8px; color: #333; }
52
+ .current-info p { color: #6c757d; font-size: 16px; }
53
+ .player-controls { display: flex; justify-content: center; align-items: center; gap: 25px; margin-bottom: 30px; }
54
+ .control-btn { background: #f1f3f5; border: none; border-radius: 50%; color: #495057; cursor: pointer; transition: all 0.3s ease; display: flex; align-items: center; justify-content: center; }
55
+ .control-btn:hover { background: #e9ecef; transform: scale(1.1); }
56
+ .control-btn.small { width: 50px; height: 50px; font-size: 18px; }
57
+ .play-btn { width: 70px; height: 70px; font-size: 28px; background: linear-gradient(135deg, #4a90e2, #3a7bc8); color: white; box-shadow: 0 8px 25px rgba(74, 144, 226, 0.3); }
58
+ .play-btn:hover { background: linear-gradient(135deg, #3a7bc8, #2d6bc1); box-shadow: 0 12px 35px rgba(74, 144, 226, 0.4); }
59
+ .progress-container { margin-bottom: 30px; padding: 0 20px; }
60
+ .progress-bar { width: 100%; height: 6px; background: #e9ecef; border-radius: 3px; cursor: pointer; margin-bottom: 10px; position: relative; }
61
+ .progress-fill { height: 100%; background: linear-gradient(90deg, #4a90e2, #6aa8e6); border-radius: 3px; width: 0%; transition: width 0.1s ease; position: relative; }
62
+ .progress-fill::after { content: ''; position: absolute; right: -2px; top: 50%; transform: translateY(-50%); width: 14px; height: 14px; background: #4a90e2; border-radius: 50%; box-shadow: 0 2px 8px rgba(74, 144, 226, 0.5); }
63
+ .time-info { display: flex; justify-content: space-between; font-size: 14px; color: #6c757d; }
64
+ .volume-container { display: flex; align-items: center; gap: 12px; margin-bottom: 30px; justify-content: center; }
65
+ .volume-icon { color: #6c757d; font-size: 18px; }
66
+ .volume-slider { width: 150px; height: 4px; background: #e9ecef; border-radius: 2px; outline: none; cursor: pointer; -webkit-appearance: none; }
67
+ .volume-slider::-webkit-slider-thumb { -webkit-appearance: none; width: 14px; height: 14px; background: #4a90e2; border-radius: 50%; cursor: pointer; }
68
+ .quality-container { display: flex; align-items: center; justify-content: space-between; margin-bottom: 25px; padding: 15px 20px; background: #f8f9fa; border-radius: 12px; border: 1px solid #e9ecef; }
69
+ .quality-label { display: flex; align-items: center; gap: 8px; color: #495057; font-size: 14px; }
70
+ .quality-select { background: #ffffff; border: 1px solid #ced4da; border-radius: 8px; color: #333; padding: 8px 12px; outline: none; cursor: pointer; font-size: 14px; }
71
+ .quality-select option { background: #ffffff; color: #333; padding: 8px; }
72
+ .download-container { display: grid; grid-template-columns: 1fr 1fr; gap: 15px; margin-bottom: 20px; }
73
+ .download-btn { display: flex; align-items: center; justify-content: center; gap: 8px; padding: 12px 15px; background: #f8f9fa; border: 1px solid #e9ecef; border-radius: 12px; color: #495057; cursor: pointer; transition: all 0.3s ease; font-size: 14px; }
74
+ .download-btn:hover:not(:disabled) { background: #eef2f7; border-color: #4a90e2; color: #4a90e2; }
75
+ .download-btn:disabled { opacity: 0.5; cursor: not-allowed; }
76
+ .lyrics-container { max-height: 500px; overflow-y: auto; scrollbar-width: thin; scrollbar-color: #ced4da transparent; padding-right: 10px; position: relative; }
77
+ .lyrics-container::-webkit-scrollbar { width: 6px; }
78
+ .lyrics-container::-webkit-scrollbar-track { background: transparent; }
79
+ .lyrics-container::-webkit-scrollbar-thumb { background: #ced4da; border-radius: 3px; }
80
+ .lyric-line { padding: 8px 15px; transition: all 0.3s ease; cursor: pointer; border-radius: 8px; margin-bottom: 6px; color: #6c757d; line-height: 1.6; text-align: center; font-size: 15px; }
81
+ .lyric-line:hover { background: #f8f9fa; color: #4a90e2; }
82
+ .lyric-line.active { color: #4a90e2; font-weight: 600; background: rgba(74, 144, 226, 0.08); transform: scale(1.02); }
83
+ .loading, .error, .empty-state { text-align: center; padding: 60px 20px; color: #6c757d; }
84
+ .loading i, .error i, .empty-state i { font-size: 48px; margin-bottom: 15px; display: block; color: #adb5bd; }
85
+ .loading i { animation: spin 1s linear infinite; color: #4a90e2; }
86
+ @keyframes spin { from { transform: rotate(0deg); } to { transform: rotate(360deg); } }
87
+ .error i { color: #e74c3c; }
88
+ .mobile-nav { display: none; position: fixed; bottom: 0; left: 0; width: 100%; background: #ffffff; box-shadow: 0 -2px 15px rgba(0, 0, 0, 0.05); z-index: 99; }
89
+ .mobile-nav-items { display: flex; justify-content: space-around; }
90
+ .mobile-nav-item { flex: 1; text-align: center; padding: 15px 0; color: #6c757d; text-decoration: none; font-size: 12px; transition: all 0.3s ease; }
91
+ .mobile-nav-item.active { color: #4a90e2; }
92
+ .mobile-nav-item i { font-size: 20px; margin-bottom: 5px; display: block; }
93
+ .view-container { display: block; }
94
+ @media (min-width: 1200px) { .desktop-layout { display: grid; grid-template-columns: 350px 450px 350px; gap: 25px; } .mobile-nav { display: none !important; } }
95
+ @media (max-width: 1199px) { .mobile-nav { display: block; } .main-container { padding-bottom: 80px; } .desktop-layout { display: block; } .view-container { display: none; } .view-container.active { display: block; } .song-name { font-size: 14px; line-height: 1.4; } .song-artist { font-size: 13px; line-height: 1.4; } .song-card { padding: 10px 12px; gap: 10px; } .song-index { width: 28px; height: 28px; font-size: 13px; } .action-btn { width: 28px; height: 28px; font-size: 12px; } }
96
+ @media (max-width: 768px) { .nav-container { flex-direction: column; gap: 15px; padding: 0 15px; } .search-container { margin: 0; max-width: none; width: 100%; } .current-cover { width: 180px; height: 180px; } .player-controls { gap: 15px; } .content-panel { padding: 15px 12px; } .panel-title { font-size: 17px; margin-bottom: 15px; } .search-results { gap: 10px; } }
97
+ ::-webkit-scrollbar { width: 8px; }
98
+ ::-webkit-scrollbar-track { background: #f8f9fa; border-radius: 4px; }
99
+ ::-webkit-scrollbar-thumb { background: #ced4da; border-radius: 4px; }
100
+ ::-webkit-scrollbar-thumb:hover { background: #adb5bd; }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
101
  </style>
102
  </head>
103
  <body>
104
+ <!-- HTML结构部分保持不变 -->
105
  <nav class="navbar">
106
  <div class="nav-container">
107
+ <div class="logo"><i class="fas fa-music"></i><span>云音乐</span></div>
 
 
 
 
108
  <div class="search-container">
109
  <div class="search-wrapper">
110
  <input type="text" class="search-input" placeholder="搜索音乐、歌手、专辑..." id="searchInput">
 
123
  <option value="qobuz">Qobuz</option>
124
  <option value="ximalaya">喜马拉雅</option>
125
  </select>
126
+ <button class="search-btn" onclick="searchMusic()"><i class="fas fa-search"></i></button>
 
 
127
  </div>
128
  </div>
129
  </div>
130
  </nav>
 
131
  <div class="main-container">
132
  <div class="desktop-layout">
133
  <div class="view-container" id="searchView">
134
  <div class="content-panel">
135
+ <h2 class="panel-title"><i class="fas fa-search"></i> 搜索结果</h2>
 
 
 
136
  <div class="search-results" id="searchResults">
137
+ <div class="empty-state"><i class="fas fa-music"></i><div>在上方搜索框输入关键词开始搜索音乐</div></div>
 
 
 
138
  </div>
139
  </div>
140
  </div>
 
141
  <div class="view-container" id="playerView">
142
  <div class="content-panel player-panel">
143
  <div class="current-song">
144
+ <div class="current-cover-container"><img class="current-cover" id="currentCover" src="data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iMjIwIiBoZWlnaHQ9IjIyMCIgdmlld0JveD0iMCAwIDIyMCAyMjAiIGZpbGw9Im5vbmUiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyI+CjxyZWN0IHdpZHRoPSIyMjAiIGhlaWdodD0iMjIwIiBmaWxsPSIjZmZmZmZmIi8+CjxjaXJjbGUgY3g9IjExMCIgY3k9IjExMCIgcj0iODAiIGZpbGw9IiNmOGY5ZmEiLz4KPHBhdGggZD0iTTEwNSAxNzVWODVIMTE1VjE3NVoiIGZpbGw9IiM0YTkwZTIiLz4KPC9zdmc+" alt="专辑封面"></div>
145
+ <div class="current-info"><h3 id="currentTitle">未选择歌曲</h3><p id="currentArtist">请搜索并选择要播放的歌曲</p></div>
 
 
 
 
 
146
  </div>
 
147
  <div class="player-controls">
148
+ <button class="control-btn small" onclick="previousSong()"><i class="fas fa-step-backward"></i></button>
149
+ <button class="control-btn play-btn" id="playBtn" onclick="togglePlay()"><i class="fas fa-play"></i></button>
150
+ <button class="control-btn small" onclick="nextSong()"><i class="fas fa-step-forward"></i></button>
 
 
 
 
 
 
151
  </div>
 
152
  <div class="progress-container">
153
+ <div class="progress-bar" onclick="seekTo(event)"><div class="progress-fill" id="progressFill"></div></div>
154
+ <div class="time-info"><span id="currentTime">0:00</span><span id="totalTime">0:00</span></div>
 
 
 
 
 
155
  </div>
 
156
  <div class="quality-container">
157
+ <div class="quality-label"><i class="fas fa-music"></i><span>音质</span></div>
 
 
 
158
  <select class="quality-select" id="qualitySelect">
159
  <option value="128">标准 128K</option>
160
  <option value="192">较高 192K</option>
 
163
  <option value="999">Hi-Res</option>
164
  </select>
165
  </div>
 
166
  <div class="volume-container">
167
  <i class="fas fa-volume-up volume-icon"></i>
168
  <input type="range" class="volume-slider" id="volumeSlider" min="0" max="100" value="80" onchange="setVolume(this.value)">
169
  </div>
 
170
  <div class="download-container">
171
+ <button class="download-btn" onclick="downloadCurrentSong()" id="downloadSongBtn" disabled><i class="fas fa-download"></i><span>下载音乐</span></button>
172
+ <button class="download-btn" onclick="downloadCurrentLyric()" id="downloadLyricBtn" disabled><i class="fas fa-file-text"></i><span>下载歌词</span></button>
 
 
 
 
 
 
173
  </div>
 
174
  <audio id="audioPlayer" preload="metadata"></audio>
175
  </div>
176
  </div>
 
177
  <div class="view-container" id="lyricsView">
178
  <div class="content-panel">
179
+ <h2 class="panel-title"><i class="fas fa-align-left"></i> 歌词</h2>
180
+ <div class="lyrics-container" id="lyricsContainer"><div class="lyric-line">暂无歌词</div></div>
 
 
 
 
 
181
  </div>
182
  </div>
183
  </div>
184
  </div>
 
185
  <div class="mobile-nav">
186
  <div class="mobile-nav-items">
187
+ <a href="#searchView" class="mobile-nav-item active" onclick="switchView('searchView', this)"><i class="fas fa-search"></i><span>搜索</span></a>
188
+ <a href="#playerView" class="mobile-nav-item" onclick="switchView('playerView', this)"><i class="fas fa-play-circle"></i><span>播放</span></a>
189
+ <a href="#lyricsView" class="mobile-nav-item" onclick="switchView('lyricsView', this)"><i class="fas fa-align-left"></i><span>歌词</span></a>
 
 
 
 
 
 
 
 
 
190
  </div>
191
  </div>
192
 
193
  <script>
194
+ // Dr.Kun: 常量和全局变量
195
  const API_BASE = 'https://music-api.gdstudio.xyz/api.php';
196
+ const PLAYER_STATE_KEY = 'cloudMusicPlayerState_v2'; // Dr.Kun: 升级版本号以避免旧数据冲突
 
 
197
  let currentPlaylist = [];
198
  let currentIndex = -1;
 
199
  let isPlaying = false;
200
+ let timeToRestore = 0; // Dr.Kun: 核心升级 - 用于暂存需要恢复的播放时间
201
 
202
  // Dr.Kun: DOM元素缓存
203
  const audioPlayer = document.getElementById('audioPlayer');
 
212
  const volumeSlider = document.getElementById('volumeSlider');
213
  const searchInput = document.getElementById('searchInput');
214
 
215
+ // Dr.Kun: 核心升级 - 保存播放器状态(现在包含播放进度)
216
  function savePlayerState() {
217
  const state = {
218
  playlist: currentPlaylist,
 
220
  volume: audioPlayer.volume,
221
  quality: qualitySelect.value,
222
  source: sourceSelect.value,
223
+ currentTime: audioPlayer.currentTime > 1 ? audioPlayer.currentTime : 0 // Dr.Kun: 只在播放超过1秒时保存进度,避免存入无效的0
224
  };
225
  try {
226
  localStorage.setItem(PLAYER_STATE_KEY, JSON.stringify(state));
227
  } catch (e) {
228
  console.error("保存状态失败:", e);
 
229
  }
230
  }
231
 
232
+ // Dr.Kun: 核心升级 - 从localStorage加载播放器状态
233
  async function loadPlayerState() {
234
  try {
235
  const savedState = localStorage.getItem(PLAYER_STATE_KEY);
236
+ if (!savedState) return;
237
+
238
+ const state = JSON.parse(savedState);
239
+
240
+ currentPlaylist = state.playlist || [];
241
+ currentIndex = state.index || -1;
242
+ timeToRestore = state.currentTime || 0; // Dr.Kun: 读取要恢复的时间
243
+
244
+ qualitySelect.value = state.quality || '320';
245
+ sourceSelect.value = state.source || 'netease';
246
+
247
+ const volumeValue = (state.volume !== undefined ? state.volume : 0.8) * 100;
248
+ volumeSlider.value = volumeValue;
249
+ setVolume(volumeValue, false); // Dr.Kun: 初始化时不保存状态,避免循环
250
+
251
+ if (currentPlaylist.length > 0) {
252
+ displaySearchResults(currentPlaylist);
253
+ }
254
+
255
+ if (currentIndex > -1 && currentPlaylist[currentIndex]) {
256
+ const song = currentPlaylist[currentIndex];
257
+ // Dr.Kun: 只更新UI,不获取URL,等待用户操作或后续逻辑
258
+ await updateCurrentSongInfo(song);
259
+ updateActiveItem();
260
+ // Dr.Kun: 关键一步 - 设置音频源,以便后续恢复进度
261
+ const quality = qualitySelect.value;
262
+ const urlResponse = await fetch(`${API_BASE}?types=url&source=${song.source}&id=${song.id}&br=${quality}`);
263
+ const urlData = await urlResponse.json();
264
+ if(urlData && urlData.url) {
265
+ audioPlayer.src = urlData.url;
266
  }
267
+
268
+ document.getElementById('downloadSongBtn').disabled = false;
269
+ document.getElementById('downloadLyricBtn').disabled = false;
270
  }
271
  } catch (e) {
272
+ console.error("加载状态失败, 数据已损坏:", e);
273
+ localStorage.removeItem(PLAYER_STATE_KEY);
274
  }
275
  }
276
 
277
  // Dr.Kun: 页面加载时执行的初始化函数
278
  async function initializeApp() {
 
279
  if (window.innerWidth >= 1200) {
280
  document.getElementById('searchView').classList.add('active');
281
  document.getElementById('playerView').classList.add('active');
 
284
  switchView('searchView', document.querySelectorAll('.mobile-nav-item')[0]);
285
  }
286
 
 
287
  await loadPlayerState();
288
 
289
+ // Dr.Kun: 监听器
290
  qualitySelect.onchange = savePlayerState;
291
  sourceSelect.onchange = savePlayerState;
 
292
 
293
+ // Dr.Kun: 核心升级 - 在页面卸载前做最后一次保存
294
+ window.addEventListener('beforeunload', savePlayerState);
295
+ // Dr.Kun: 核心升级 - 定期保存进度
296
+ setInterval(savePlayerState, 5000);
 
 
 
 
297
  }
298
 
299
+ // ... (其他函数基本不变,除了setVolume增加一个参数) ...
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
300
 
301
+ function setVolume(value, shouldSave = true) { // Dr.Kun: 增加一个参数判断是否需要保存
302
+ audioPlayer.volume = value / 100;
303
+ const volumeIcon = document.querySelector('.volume-icon');
304
+ if (value == 0) volumeIcon.className = 'fas fa-volume-mute volume-icon';
305
+ else if (value < 50) volumeIcon.className = 'fas fa-volume-down volume-icon';
306
+ else volumeIcon.className = 'fas fa-volume-up volume-icon';
307
+ if (shouldSave) {
308
+ savePlayerState();
 
 
 
309
  }
310
  }
311
+
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
312
  async function playSong(index) {
313
  if (index < 0 || index >= currentPlaylist.length) return;
314
  currentIndex = index;
315
+ timeToRestore = 0; // Dr.Kun: 用户主动切歌,不再需要恢复旧进度
316
  const song = currentPlaylist[index];
317
  await updateCurrentSongInfo(song);
318
  updateActiveItem();
319
+ // ... (playSong的后续逻辑不变)
 
320
  try {
321
  showNotification('正在加载音乐...', 'info');
322
  const quality = qualitySelect.value;
 
333
  const playPromise = audioPlayer.play();
334
  if (playPromise !== undefined) {
335
  playPromise.then(() => {
 
 
 
336
  showNotification(`开始播放: ${song.name}`, 'success');
337
  }).catch(error => {
 
338
  showNotification('自动播放失败,请手动点击播放', 'warning');
339
  });
340
  }
341
  } else {
342
+ showNotification('无法获取音乐链接', 'error');
343
  }
344
  } catch (error) {
345
  console.error('播放失败:', error);
 
347
  }
348
  }
349
 
350
+ // Dr.Kun: 核心升级 - 在元数据加载后恢复播放进度
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
351
  audioPlayer.addEventListener('loadedmetadata', () => {
352
  totalTimeSpan.textContent = formatTime(audioPlayer.duration);
353
+ if (timeToRestore > 0 && timeToRestore < audioPlayer.duration) {
354
+ audioPlayer.currentTime = timeToRestore;
355
+ timeToRestore = 0; // Dr.Kun: 恢复后立即清零,避免影响下次播放
356
+ updateProgress(); // 立即更新一次进度条显示
357
+ showNotification('已恢复上次播放进度', 'info');
358
+ }
359
  });
360
 
361
+ // Dr.Kun: 保持其他事件监听和函数不变
362
+ audioPlayer.addEventListener('play', () => { isPlaying = true; updatePlayButton(); currentCover.classList.add('playing'); });
363
+ audioPlayer.addEventListener('pause', () => { isPlaying = false; updatePlayButton(); currentCover.classList.remove('playing'); savePlayerState(); }); // Dr.Kun: 暂停时也保存一下
364
+ audioPlayer.addEventListener('ended', nextSong);
365
  setInterval(updateProgress, 500);
366
+ searchInput.addEventListener('keypress', (e) => { if (e.key === 'Enter') searchMusic(); });
367
+ document.addEventListener('keydown', (e) => { if (['INPUT', 'TEXTAREA'].includes(document.activeElement.tagName)) return; const keyMap = { ' ': togglePlay, 'ArrowRight': () => { if(audioPlayer.duration) audioPlayer.currentTime = Math.min(audioPlayer.currentTime + 5, audioPlayer.duration); }, 'ArrowLeft': () => { if(audioPlayer.duration) audioPlayer.currentTime = Math.max(audioPlayer.currentTime - 5, 0); }, 'ArrowUp': () => { const v = Math.min(parseInt(volumeSlider.value) + 5, 100); volumeSlider.value = v; setVolume(v); }, 'ArrowDown': () => { const v = Math.max(parseInt(volumeSlider.value) - 5, 0); volumeSlider.value = v; setVolume(v); }, 'n': nextSong, 'N': nextSong, 'p': previousSong, 'P': previousSong }; if (keyMap[e.key]) { e.preventDefault(); keyMap[e.key](); } });
368
+ function switchView(viewId, navItem) { document.querySelectorAll('.view-container').forEach(view => view.classList.remove('active')); document.getElementById(viewId).classList.add('active'); if (navItem) { document.querySelectorAll('.mobile-nav-item').forEach(item => item.classList.remove('active')); navItem.classList.add('active'); } }
369
+ async function searchMusic() { const keyword = searchInput.value.trim(); const source = sourceSelect.value; if (!keyword) { showNotification('请输入搜索关键词', 'warning'); return; } const resultsContainer = document.getElementById('searchResults'); resultsContainer.innerHTML = `<div class="loading"><i class="fas fa-spinner"></i><div>正在搜索音乐...</div></div>`; try { const response = await fetch(`${API_BASE}?types=search&source=${source}&name=${encodeURIComponent(keyword)}&count=30`); const data = await response.json(); if (data && data.length > 0) { currentPlaylist = data; currentIndex = -1; displaySearchResults(data); savePlayerState(); } else { resultsContainer.innerHTML = `<div class="error"><i class="fas fa-exclamation-triangle"></i><div>未找到相关歌曲</div></div>`; } } catch (error) { console.error('搜索失败:', error); resultsContainer.innerHTML = `<div class="error"><i class="fas fa-wifi"></i><div>网络连接失败</div></div>`; } }
370
+ async function getAlbumCoverUrl(song, size = 300) { if (!song || !song.pic_id) { return 'data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iMjIwIiBoZWlnaHQ9IjIyMCIgdmlld0JveD0iMCAwIDIyMCAyMjAiIGZpbGw9Im5vbmUiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyI+CjxyZWN0IHdpZHRoPSIyMjAiIGhlaWdodD0iMjIwIiBmaWxsPSIjZmZmZmZmIi8+CjxjaXJjbGUgY3g9IjExMCIgY3k9IjExMCIgcj0iODAiIGZpbGw9IiNmOGY5ZmEiLz4KPHBhdGggZD0iTTEwNSAxNzVWODVIMTE1VjE3NVoiIGZpbGw9IiM0YTkwZTIiLz4KPC9zdmc+'; } try { const response = await fetch(`${API_BASE}?types=pic&source=${song.source}&id=${song.pic_id}&size=${size}`); const data = await response.json(); return (data && data.url) ? data.url : 'data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iMjIwIiBoZWlnaHQ9IjIyMCIgdmlld0JveD0iMCAwIDIyMCAyMjAiIGZpbGw9Im5vbmUiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyI+CjxyZWN0IHdpZHRoPSIyMjAiIGhlaWdodD0iMjIwIiBmaWxsPSIjZmZmZmZmIi8+CjxjaXJjbGUgY3g9IjExMCIgY3k9IjExMCIgcj0iODAiIGZpbGw9IiNmOGY5ZmEiLz4KPHBhdGggZD0iTTEwNSAxNzVWODVIMTE1VjE3NVoiIGZpbGw9IiM0YTkwZTIiLz4KPC9zdmc+'; } catch (error) { console.error('获取专辑图失败:', error); return 'data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iMjIwIiBoZWlnaHQ9IjIyMCIgdmlld0JveD0iMCAwIDIyMCAyMjAiIGZpbGw9Im5vbmUiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyI+CjxyZWN0IHdpZHRoPSIyMjAiIGhlaWdodD0iMjIwIiBmaWxsPSIjZmZmZmZmIi8+CjxjaXJjbGUgY3g9IjExMCIgY3k9IjExMCIgcj0iODAiIGZpbGw9IiNmOGY5ZmEiLz4KPHBhdGggZD0iTTEwNSAxNzVWODVIMTE1VjE3NVoiIGZpbGw9IiM0YTkwZTIiLz4KPC9zdmc+'; } }
371
+ function displaySearchResults(songs) { const resultsContainer = document.getElementById('searchResults'); resultsContainer.innerHTML = ''; songs.forEach((song, index) => { const songCard = document.createElement('div'); songCard.className = 'song-card'; songCard.onclick = () => playSong(index); const artistText = Array.isArray(song.artist) ? song.artist.join(' / ') : song.artist; songCard.innerHTML = ` <div class="song-index">${(index + 1).toString().padStart(2, '0')}</div> <div class="song-info"> <div class="song-name">${song.name}</div> <div class="song-artist">${artistText} ${song.album ? '· ' + song.album : ''}</div> </div> <div class="song-actions"> <button class="action-btn" onclick="event.stopPropagation(); downloadSong(${index})" title="下载音乐"><i class="fas fa-download"></i></button> <button class="action-btn" onclick="event.stopPropagation(); downloadLyric(${index})" title="下载歌词"><i class="fas fa-file-text"></i></button> </div>`; resultsContainer.appendChild(songCard); }); }
372
+ async function downloadSong(index) { const song = currentPlaylist[index]; const quality = qualitySelect.value; try { showNotification('正在获取下载链接...', 'info'); const response = await fetch(`${API_BASE}?types=url&source=${song.source}&id=${song.id}&br=${quality}`); const data = await response.json(); if (data && data.url) { const link = document.createElement('a'); link.href = data.url; link.download = `${song.name} - ${Array.isArray(song.artist) ? song.artist.join(', ') : song.artist}.mp3`; link.target = '_blank'; document.body.appendChild(link); link.click(); document.body.removeChild(link); showNotification('开始下载音乐文件', 'success'); } else { showNotification('无法获取下载链接', 'error'); } } catch (error) { console.error('下载失败:', error); showNotification('下载失败,请稍后重试', 'error'); } }
373
+ async function downloadLyric(index) { const song = currentPlaylist[index]; try { showNotification('正在获取歌词...', 'info'); const response = await fetch(`${API_BASE}?types=lyric&source=${song.source}&id=${song.lyric_id || song.id}`); const data = await response.json(); if (data && data.lyric) { let lyricContent = `歌曲:${song.name}\r\n歌手:${Array.isArray(song.artist) ? song.artist.join(', ') : song.artist}\r\n专辑:${song.album}\r\n\r\n${data.lyric}`; if (data.tlyric) lyricContent += `\r\n\r\n=== 翻译 ===\r\n${data.tlyric}`; const blob = new Blob([lyricContent], { type: 'text/plain;charset=utf-8' }); const url = URL.createObjectURL(blob); const link = document.createElement('a'); link.href = url; link.download = `${song.name} - ${Array.isArray(song.artist) ? song.artist.join(', ') : song.artist}.lrc`; document.body.appendChild(link); link.click(); document.body.removeChild(link); URL.revokeObjectURL(url); showNotification('歌词下载完成', 'success'); } else { showNotification('该歌曲暂无歌词', 'warning'); } } catch (error) { console.error('下载歌词失败:', error); showNotification('下载歌词失败,请稍后重试', 'error'); } }
374
+ async function downloadCurrentSong() { if (currentIndex === -1) { showNotification('请先选择歌曲', 'warning'); return; } await downloadSong(currentIndex); }
375
+ async function downloadCurrentLyric() { if (currentIndex === -1) { showNotification('请先选择歌曲', 'warning'); return; } await downloadLyric(currentIndex); }
376
+ async function updateCurrentSongInfo(song) { document.getElementById('currentTitle').textContent = song.name; const artistText = Array.isArray(song.artist) ? song.artist.join(' / ') : song.artist; document.getElementById('currentArtist').textContent = `${artistText}${song.album ? ' · ' + song.album : ''}`; currentCover.src = await getAlbumCoverUrl(song, 500); }
377
+ function updateActiveItem() { document.querySelectorAll('.song-card').forEach((item, index) => { item.classList.toggle('active', index === currentIndex); }); }
378
+ function updatePlayButton() { playBtn.querySelector('i').className = isPlaying ? 'fas fa-pause' : 'fas fa-play'; }
379
+ function togglePlay() { if (!audioPlayer.src) { showNotification('请先选择一首歌曲', 'warning'); return; } if (isPlaying) { audioPlayer.pause(); } else { audioPlayer.play().catch(e => console.error("播放命令被拒绝", e)); } }
380
+ function seekTo(event) { if(!audioPlayer.duration) return; const progressBar = event.currentTarget; const rect = progressBar.getBoundingClientRect(); audioPlayer.currentTime = ((event.clientX - rect.left) / rect.width) * audioPlayer.duration; }
381
+ async function loadLyrics(song) { try { const response = await fetch(`${API_BASE}?types=lyric&source=${song.source}&id=${song.lyric_id || song.id}`); const data = await response.json(); let lyrics = []; if (data && data.lyric) { lyrics = parseLyrics(data.lyric); if (data.tlyric) { const tLyrics = parseLyrics(data.tlyric); mergeLyrics(lyrics, tLyrics); } } displayLyrics(lyrics); } catch (error) { console.error('加载歌词失败:', error); lyricsContainer.innerHTML = '<div class="lyric-line">加载歌词失败</div>'; } }
382
+ function parseLyrics(lyricText) { const lines = lyricText.split('\n'); const lyrics = []; const timeRegex = /\[(\d{2}):(\d{2})[.:](\d{2,3})\]/g; for (const line of lines) { if (!line.trim()) continue; let text = line.replace(timeRegex, '').trim(); if (text) { let match; timeRegex.lastIndex = 0; while ((match = timeRegex.exec(line)) !== null) { const timeInSeconds = parseInt(match[1]) * 60 + parseInt(match[2]) + parseInt(match[3]) / (match[3].length === 3 ? 1000 : 100); lyrics.push({ time: timeInSeconds, text: text }); } } } return lyrics.sort((a, b) => a.time - b.time); }
383
+ function mergeLyrics(originalLyrics, translatedLyrics) { const translatedMap = new Map(translatedLyrics.map(l => [Math.round(l.time * 10), l.text])); originalLyrics.forEach(l => { const key = Math.round(l.time * 10); const translatedText = translatedMap.get(key); if (translatedText) { l.text += `<br><span style="color: #adb5bd; font-size: 0.9em;">${translatedText}</span>`; } }); }
384
+ function displayLyrics(lyrics) { lyricsContainer.innerHTML = lyrics.length === 0 ? '<div class="lyric-line">暂无歌词</div>' : ''; lyrics.forEach(lyric => { const line = document.createElement('div'); line.className = 'lyric-line'; line.innerHTML = lyric.text; line.setAttribute('data-time', lyric.time); line.onclick = () => { if(audioPlayer.duration) audioPlayer.currentTime = lyric.time; }; lyricsContainer.appendChild(line); }); }
385
+ function updateProgress() { if (audioPlayer.duration) { progressFill.style.width = `${(audioPlayer.currentTime / audioPlayer.duration) * 100}%`; currentTimeSpan.textContent = formatTime(audioPlayer.currentTime); highlightCurrentLyric(); } }
386
+ function formatTime(seconds) { const min = Math.floor(seconds / 60); const sec = Math.floor(seconds % 60); return `${min}:${sec.toString().padStart(2, '0')}`; }
387
+ function highlightCurrentLyric() { const currentTime = audioPlayer.currentTime; const lyricLines = document.querySelectorAll('.lyric-line'); if (lyricLines.length === 0 || lyricsContainer.offsetParent === null) return; let activeIndex = -1; for (let i = lyricLines.length - 1; i >= 0; i--) { if (parseFloat(lyricLines[i].getAttribute('data-time')) <= currentTime + 0.2) { activeIndex = i; break; } } lyricLines.forEach((line, index) => { if (line.classList.contains('active') !== (index === activeIndex)) { line.classList.toggle('active', index === activeIndex); } }); if (activeIndex > -1) { const activeLine = lyricLines[activeIndex]; const scrollPosition = activeLine.offsetTop - lyricsContainer.clientHeight / 2 + activeLine.clientHeight / 2; lyricsContainer.scrollTo({ top: scrollPosition, behavior: 'smooth' }); } }
388
+ function showNotification(message, type = 'info') { const existing = document.querySelector('.custom-notification'); if (existing) existing.remove(); const notification = document.createElement('div'); notification.className = `custom-notification notification-${type}`; const icons = { info: 'info-circle', success: 'check-circle', error: 'exclamation-circle', warning: 'exclamation-triangle' }; notification.innerHTML = `<i class="fas fa-${icons[type]}"></i><span>${message}</span>`; document.body.appendChild(notification); Object.assign(notification.style, { position: 'fixed', bottom: '20px', right: '20px', padding: '12px 20px', borderRadius: '8px', boxShadow: '0 4px 12px rgba(0,0,0,0.15)', display: 'flex', alignItems: 'center', gap: '10px', fontSize: '14px', zIndex: '9999', transition: 'all 0.4s cubic-bezier(0.25, 0.8, 0.25, 1)', transform: 'translateY(100px)', opacity: '0' }); const colors = { success: { bg: 'rgba(40, 167, 69, 0.9)', color: '#fff' }, error: { bg: 'rgba(220, 53, 69, 0.9)', color: '#fff' }, warning: { bg: 'rgba(255, 193, 7, 0.9)', color: '#212529' }, info: { bg: 'rgba(74, 144, 226, 0.9)', color: '#fff' } }; notification.style.backgroundColor = colors[type].bg; notification.style.color = colors[type].color; setTimeout(() => { notification.style.transform = 'translateY(0)'; notification.style.opacity = '1'; }, 50); setTimeout(() => { notification.style.transform = 'translateY(100px)'; notification.style.opacity = '0'; setTimeout(() => notification.remove(), 400); }, 3000); }
389
+ function previousSong() { if (currentPlaylist.length === 0) return; currentIndex = (currentIndex - 1 + currentPlaylist.length) % currentPlaylist.length; playSong(currentIndex); }
390
+ function nextSong() { if (currentPlaylist.length === 0) return; currentIndex = (currentIndex + 1) % currentPlaylist.length; playSong(currentIndex); }
391
 
392
+ // Dr.Kun: 启动应用
393
  initializeApp();
394
  </script>
395
  </body>