Finsqw commited on
Commit
5885838
·
verified ·
1 Parent(s): 9894278

Add 1 files

Browse files
Files changed (1) hide show
  1. index.html +411 -1
index.html CHANGED
@@ -68,6 +68,13 @@
68
  10%, 30%, 50%, 70%, 90% { transform: translateX(-5px); }
69
  20%, 40%, 60%, 80% { transform: translateX(5px); }
70
  }
 
 
 
 
 
 
 
71
  </style>
72
  </head>
73
  <body class="bg-gray-100 text-gray-800 font-sans">
@@ -151,7 +158,85 @@
151
 
152
  <!-- 音乐播放器 (初始隐藏) -->
153
  <div id="playerPanel" class="hidden">
154
- <!-- 播放器内容保持不变 -->
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
155
  </div>
156
  </div>
157
 
@@ -164,6 +249,10 @@
164
  let isConnected = false;
165
  let retryCount = 0;
166
  const MAX_RETRIES = 2;
 
 
 
 
167
 
168
  // DOM元素
169
  const connectBtn = document.getElementById('connectBtn');
@@ -176,6 +265,22 @@
176
  const connectionTestPanel = document.getElementById('connectionTestPanel');
177
  const connectionTestMessage = document.getElementById('connectionTestMessage');
178
  const playerPanel = document.getElementById('playerPanel');
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
179
 
180
  // 初始化
181
  document.addEventListener('DOMContentLoaded', () => {
@@ -190,6 +295,31 @@
190
 
191
  // 事件监听器
192
  connectBtn.addEventListener('click', handleConnect);
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
193
 
194
  // 检查WebDAV库是否加载成功
195
  checkWebDAVLibrary();
@@ -281,6 +411,9 @@
281
  // 显示播放器面板
282
  playerPanel.classList.remove('hidden');
283
 
 
 
 
284
  } catch (error) {
285
  console.error('连接失败:', error);
286
  isConnected = false;
@@ -327,6 +460,283 @@
327
  }
328
  }
329
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
330
  // 更新连接状态显示
331
  function updateConnectionStatus(connected, message, isConnecting = false) {
332
  const statusDot = connectionStatus.querySelector('span:first-child');
 
68
  10%, 30%, 50%, 70%, 90% { transform: translateX(-5px); }
69
  20%, 40%, 60%, 80% { transform: translateX(5px); }
70
  }
71
+ .fade-in {
72
+ animation: fadeIn 0.5s ease-in;
73
+ }
74
+ @keyframes fadeIn {
75
+ from { opacity: 0; }
76
+ to { opacity: 1; }
77
+ }
78
  </style>
79
  </head>
80
  <body class="bg-gray-100 text-gray-800 font-sans">
 
158
 
159
  <!-- 音乐播放器 (初始隐藏) -->
160
  <div id="playerPanel" class="hidden">
161
+ <div class="bg-white rounded-xl shadow-lg p-6 mb-6 border border-gray-200 fade-in">
162
+ <div class="flex justify-between items-center mb-6">
163
+ <h2 class="text-xl font-semibold flex items-center">
164
+ <i class="fas fa-music mr-2 text-blue-500"></i>
165
+ <span>音乐库</span>
166
+ </h2>
167
+ <div class="flex items-center space-x-2">
168
+ <button id="refreshBtn" class="px-3 py-1 bg-gray-100 text-gray-700 rounded-md hover:bg-gray-200 transition-colors duration-200 flex items-center">
169
+ <i class="fas fa-sync-alt mr-1"></i>
170
+ <span>刷新</span>
171
+ </button>
172
+ <div class="relative">
173
+ <input type="text" id="searchInput" placeholder="搜索音乐..."
174
+ class="pl-8 pr-3 py-1 border border-gray-300 rounded-md focus:outline-none focus:ring-1 focus:ring-blue-500">
175
+ <div class="absolute inset-y-0 left-0 pl-2 flex items-center pointer-events-none">
176
+ <i class="fas fa-search text-gray-400"></i>
177
+ </div>
178
+ </div>
179
+ </div>
180
+ </div>
181
+
182
+ <div id="loadingIndicator" class="text-center py-8 hidden">
183
+ <div class="inline-block animate-spin rounded-full h-8 w-8 border-t-2 border-b-2 border-blue-500 mb-2"></div>
184
+ <p class="text-gray-600">正在扫描音乐文件...</p>
185
+ </div>
186
+
187
+ <div id="musicList" class="space-y-2 max-h-96 overflow-y-auto">
188
+ <!-- 音乐列表将在这里动态生成 -->
189
+ <div class="text-center py-8 text-gray-500">
190
+ <i class="fas fa-music fa-2x mb-2"></i>
191
+ <p>连接成功后,音乐将显示在这里</p>
192
+ </div>
193
+ </div>
194
+
195
+ <div id="playerControls" class="mt-6 bg-gray-50 p-4 rounded-lg hidden">
196
+ <div class="flex items-center space-x-4 mb-4">
197
+ <div class="flex-shrink-0">
198
+ <img id="currentAlbumArt" src="https://via.placeholder.com/80" alt="专辑封面" class="w-16 h-16 rounded-md album-art">
199
+ </div>
200
+ <div class="flex-grow">
201
+ <h3 id="currentSongTitle" class="font-medium">未选择歌曲</h3>
202
+ <p id="currentSongArtist" class="text-sm text-gray-600">未知艺术家</p>
203
+ </div>
204
+ <div class="flex-shrink-0">
205
+ <button id="playPauseBtn" class="w-10 h-10 bg-blue-600 text-white rounded-full flex items-center justify-center hover:bg-blue-700 transition-colors duration-200">
206
+ <i class="fas fa-play"></i>
207
+ </button>
208
+ </div>
209
+ </div>
210
+
211
+ <div class="progress-container mb-2">
212
+ <div class="flex justify-between text-xs text-gray-500 mb-1">
213
+ <span id="currentTime">0:00</span>
214
+ <span id="duration">0:00</span>
215
+ </div>
216
+ <div class="w-full bg-gray-200 rounded-full h-1.5 cursor-pointer">
217
+ <div id="progressBar" class="bg-blue-600 h-1.5 rounded-full w-0"></div>
218
+ </div>
219
+ </div>
220
+
221
+ <div class="flex justify-between items-center mt-4">
222
+ <div class="flex items-center space-x-4">
223
+ <button id="prevBtn" class="text-gray-700 hover:text-blue-600 transition-colors duration-200">
224
+ <i class="fas fa-step-backward"></i>
225
+ </button>
226
+ <button id="nextBtn" class="text-gray-700 hover:text-blue-600 transition-colors duration-200">
227
+ <i class="fas fa-step-forward"></i>
228
+ </button>
229
+ </div>
230
+ <div class="flex items-center space-x-2">
231
+ <button id="volumeBtn" class="text-gray-700 hover:text-blue-600 transition-colors duration-200">
232
+ <i class="fas fa-volume-up"></i>
233
+ </button>
234
+ <input type="range" id="volumeControl" min="0" max="1" step="0.01" value="0.7"
235
+ class="w-24 accent-blue-600 cursor-pointer">
236
+ </div>
237
+ </div>
238
+ </div>
239
+ </div>
240
  </div>
241
  </div>
242
 
 
249
  let isConnected = false;
250
  let retryCount = 0;
251
  const MAX_RETRIES = 2;
252
+ let musicFiles = [];
253
+ let currentAudio = new Audio();
254
+ let currentTrackIndex = -1;
255
+ let isPlaying = false;
256
 
257
  // DOM元素
258
  const connectBtn = document.getElementById('connectBtn');
 
265
  const connectionTestPanel = document.getElementById('connectionTestPanel');
266
  const connectionTestMessage = document.getElementById('connectionTestMessage');
267
  const playerPanel = document.getElementById('playerPanel');
268
+ const musicList = document.getElementById('musicList');
269
+ const loadingIndicator = document.getElementById('loadingIndicator');
270
+ const playerControls = document.getElementById('playerControls');
271
+ const refreshBtn = document.getElementById('refreshBtn');
272
+ const searchInput = document.getElementById('searchInput');
273
+ const currentAlbumArt = document.getElementById('currentAlbumArt');
274
+ const currentSongTitle = document.getElementById('currentSongTitle');
275
+ const currentSongArtist = document.getElementById('currentSongArtist');
276
+ const playPauseBtn = document.getElementById('playPauseBtn');
277
+ const prevBtn = document.getElementById('prevBtn');
278
+ const nextBtn = document.getElementById('nextBtn');
279
+ const volumeBtn = document.getElementById('volumeBtn');
280
+ const volumeControl = document.getElementById('volumeControl');
281
+ const progressBar = document.getElementById('progressBar');
282
+ const currentTime = document.getElementById('currentTime');
283
+ const duration = document.getElementById('duration');
284
 
285
  // 初始化
286
  document.addEventListener('DOMContentLoaded', () => {
 
295
 
296
  // 事件监听器
297
  connectBtn.addEventListener('click', handleConnect);
298
+ refreshBtn.addEventListener('click', scanMusicFiles);
299
+ searchInput.addEventListener('input', filterMusicList);
300
+ playPauseBtn.addEventListener('click', togglePlayPause);
301
+ prevBtn.addEventListener('click', playPreviousTrack);
302
+ nextBtn.addEventListener('click', playNextTrack);
303
+ volumeControl.addEventListener('input', adjustVolume);
304
+ volumeBtn.addEventListener('click', toggleMute);
305
+
306
+ // 进度条点击事件
307
+ document.querySelector('.progress-container').addEventListener('click', (e) => {
308
+ if (!currentAudio.src) return;
309
+
310
+ const progressContainer = e.currentTarget;
311
+ const clickPosition = e.clientX - progressContainer.getBoundingClientRect().left;
312
+ const containerWidth = progressContainer.clientWidth;
313
+ const seekPercentage = clickPosition / containerWidth;
314
+ const seekTime = seekPercentage * currentAudio.duration;
315
+
316
+ currentAudio.currentTime = seekTime;
317
+ });
318
+
319
+ // 音频事件监听
320
+ currentAudio.addEventListener('timeupdate', updateProgressBar);
321
+ currentAudio.addEventListener('ended', playNextTrack);
322
+ currentAudio.addEventListener('loadedmetadata', updateDuration);
323
 
324
  // 检查WebDAV库是否加载成功
325
  checkWebDAVLibrary();
 
411
  // 显示播放器面板
412
  playerPanel.classList.remove('hidden');
413
 
414
+ // 扫描音乐文件
415
+ await scanMusicFiles();
416
+
417
  } catch (error) {
418
  console.error('连接失败:', error);
419
  isConnected = false;
 
460
  }
461
  }
462
 
463
+ // 扫描音乐文件
464
+ async function scanMusicFiles() {
465
+ if (!isConnected || !webDAVClient) {
466
+ showError('请先连接到WebDAV服务器');
467
+ return;
468
+ }
469
+
470
+ try {
471
+ // 显示加载指示器
472
+ loadingIndicator.classList.remove('hidden');
473
+ musicList.innerHTML = '';
474
+
475
+ // 递归扫描所有目录中的音乐文件
476
+ musicFiles = [];
477
+ await scanDirectory('/');
478
+
479
+ // 隐藏加载指示器
480
+ loadingIndicator.classList.add('hidden');
481
+
482
+ if (musicFiles.length === 0) {
483
+ musicList.innerHTML = `
484
+ <div class="text-center py-8 text-gray-500">
485
+ <i class="fas fa-exclamation-circle fa-2x mb-2"></i>
486
+ <p>未找到音乐文件</p>
487
+ <p class="text-sm mt-2">支持的格式: .mp3, .ogg, .wav, .flac, .m4a</p>
488
+ </div>
489
+ `;
490
+ playerControls.classList.add('hidden');
491
+ } else {
492
+ renderMusicList();
493
+ playerControls.classList.remove('hidden');
494
+ }
495
+
496
+ } catch (error) {
497
+ console.error('扫描音乐文件失败:', error);
498
+ loadingIndicator.classList.add('hidden');
499
+ showError(`扫描音乐文件失败: ${error.message}`);
500
+ }
501
+ }
502
+
503
+ // 递归扫描目录
504
+ async function scanDirectory(path) {
505
+ try {
506
+ const contents = await webDAVClient.getDirectoryContents(path);
507
+
508
+ for (const item of contents) {
509
+ if (item.type === 'directory') {
510
+ // 如果是目录,递归扫描
511
+ await scanDirectory(item.filename);
512
+ } else if (isMusicFile(item.filename)) {
513
+ // 如果是音乐文件,添加到列表
514
+ musicFiles.push({
515
+ filename: item.filename,
516
+ basename: item.basename,
517
+ lastmod: item.lastmod,
518
+ size: item.size,
519
+ path: path
520
+ });
521
+ }
522
+ }
523
+ } catch (error) {
524
+ console.error(`扫描目录 ${path} 失败:`, error);
525
+ throw error;
526
+ }
527
+ }
528
+
529
+ // 检查是否是音乐文件
530
+ function isMusicFile(filename) {
531
+ const musicExtensions = ['.mp3', '.ogg', '.wav', '.flac', '.m4a'];
532
+ return musicExtensions.some(ext => filename.toLowerCase().endsWith(ext));
533
+ }
534
+
535
+ // 渲染音乐列表
536
+ function renderMusicList(filterText = '') {
537
+ musicList.innerHTML = '';
538
+
539
+ const filteredFiles = filterText
540
+ ? musicFiles.filter(file =>
541
+ file.basename.toLowerCase().includes(filterText.toLowerCase()))
542
+ : musicFiles;
543
+
544
+ if (filteredFiles.length === 0) {
545
+ musicList.innerHTML = `
546
+ <div class="text-center py-8 text-gray-500">
547
+ <i class="fas fa-search fa-2x mb-2"></i>
548
+ <p>没有找到匹配的音乐</p>
549
+ </div>
550
+ `;
551
+ return;
552
+ }
553
+
554
+ filteredFiles.forEach((file, index) => {
555
+ const musicItem = document.createElement('div');
556
+ musicItem.className = `flex items-center p-3 hover:bg-gray-50 rounded-md cursor-pointer transition-colors duration-200 ${currentTrackIndex === index ? 'bg-blue-50' : ''}`;
557
+ musicItem.innerHTML = `
558
+ <div class="flex-shrink-0 w-10 h-10 bg-gray-200 rounded-md flex items-center justify-center text-gray-500">
559
+ <i class="fas fa-music"></i>
560
+ </div>
561
+ <div class="ml-3 flex-grow">
562
+ <h3 class="text-sm font-medium truncate">${file.basename}</h3>
563
+ <p class="text-xs text-gray-500">${formatFileSize(file.size)} • ${formatDate(file.lastmod)}</p>
564
+ </div>
565
+ <div class="ml-2 text-gray-400">
566
+ <i class="fas fa-play"></i>
567
+ </div>
568
+ `;
569
+
570
+ musicItem.addEventListener('click', () => playTrack(index));
571
+ musicList.appendChild(musicItem);
572
+ });
573
+ }
574
+
575
+ // 过滤音乐列表
576
+ function filterMusicList() {
577
+ renderMusicList(searchInput.value.trim());
578
+ }
579
+
580
+ // 播放指定曲目
581
+ async function playTrack(index) {
582
+ if (index < 0 || index >= musicFiles.length) return;
583
+
584
+ currentTrackIndex = index;
585
+ const file = musicFiles[index];
586
+
587
+ try {
588
+ // 更新UI显示当前播放的曲目
589
+ renderMusicList(searchInput.value.trim());
590
+
591
+ // 获取音乐文件的URL
592
+ const fileUrl = await webDAVClient.getFileDownloadLink(file.filename);
593
+
594
+ // 设置音频源
595
+ currentAudio.src = fileUrl;
596
+ currentAudio.load();
597
+
598
+ // 更新播放器信息
599
+ currentSongTitle.textContent = file.basename;
600
+ currentSongArtist.textContent = 'WebDAV音乐';
601
+ currentAlbumArt.src = 'https://via.placeholder.com/80';
602
+
603
+ // 播放音乐
604
+ currentAudio.play()
605
+ .then(() => {
606
+ isPlaying = true;
607
+ playPauseBtn.innerHTML = '<i class="fas fa-pause"></i>';
608
+ })
609
+ .catch(error => {
610
+ console.error('播放失败:', error);
611
+ showError('播放失败: ' + error.message);
612
+ });
613
+
614
+ } catch (error) {
615
+ console.error('获取音乐文件失败:', error);
616
+ showError('获取音乐文件失败: ' + error.message);
617
+ }
618
+ }
619
+
620
+ // 切换播放/暂停
621
+ function togglePlayPause() {
622
+ if (!currentAudio.src) {
623
+ if (musicFiles.length > 0) {
624
+ playTrack(0);
625
+ }
626
+ return;
627
+ }
628
+
629
+ if (isPlaying) {
630
+ currentAudio.pause();
631
+ playPauseBtn.innerHTML = '<i class="fas fa-play"></i>';
632
+ } else {
633
+ currentAudio.play()
634
+ .then(() => {
635
+ playPauseBtn.innerHTML = '<i class="fas fa-pause"></i>';
636
+ })
637
+ .catch(error => {
638
+ console.error('播放失败:', error);
639
+ showError('播放失败: ' + error.message);
640
+ });
641
+ }
642
+
643
+ isPlaying = !isPlaying;
644
+ }
645
+
646
+ // 播放上一曲
647
+ function playPreviousTrack() {
648
+ if (musicFiles.length === 0) return;
649
+
650
+ let newIndex = currentTrackIndex - 1;
651
+ if (newIndex < 0) {
652
+ newIndex = musicFiles.length - 1;
653
+ }
654
+
655
+ playTrack(newIndex);
656
+ }
657
+
658
+ // 播放下一曲
659
+ function playNextTrack() {
660
+ if (musicFiles.length === 0) return;
661
+
662
+ let newIndex = currentTrackIndex + 1;
663
+ if (newIndex >= musicFiles.length) {
664
+ newIndex = 0;
665
+ }
666
+
667
+ playTrack(newIndex);
668
+ }
669
+
670
+ // 调整音量
671
+ function adjustVolume() {
672
+ currentAudio.volume = volumeControl.value;
673
+
674
+ // 更新音量按钮图标
675
+ if (currentAudio.volume === 0) {
676
+ volumeBtn.innerHTML = '<i class="fas fa-volume-mute"></i>';
677
+ } else if (currentAudio.volume < 0.5) {
678
+ volumeBtn.innerHTML = '<i class="fas fa-volume-down"></i>';
679
+ } else {
680
+ volumeBtn.innerHTML = '<i class="fas fa-volume-up"></i>';
681
+ }
682
+ }
683
+
684
+ // 切换静音
685
+ function toggleMute() {
686
+ if (currentAudio.volume === 0) {
687
+ // 如果静音,恢复之前的音量
688
+ currentAudio.volume = volumeControl.value;
689
+ volumeBtn.innerHTML = currentAudio.volume < 0.5
690
+ ? '<i class="fas fa-volume-down"></i>'
691
+ : '<i class="fas fa-volume-up"></i>';
692
+ } else {
693
+ // 如果没静音,保存当前音量并静音
694
+ volumeControl.value = currentAudio.volume;
695
+ currentAudio.volume = 0;
696
+ volumeBtn.innerHTML = '<i class="fas fa-volume-mute"></i>';
697
+ }
698
+ }
699
+
700
+ // 更新进度条
701
+ function updateProgressBar() {
702
+ if (!isNaN(currentAudio.duration)) {
703
+ const progress = (currentAudio.currentTime / currentAudio.duration) * 100;
704
+ progressBar.style.width = `${progress}%`;
705
+ currentTime.textContent = formatTime(currentAudio.currentTime);
706
+ }
707
+ }
708
+
709
+ // 更新总时长
710
+ function updateDuration() {
711
+ if (!isNaN(currentAudio.duration)) {
712
+ duration.textContent = formatTime(currentAudio.duration);
713
+ }
714
+ }
715
+
716
+ // 格式化时间 (秒 -> MM:SS)
717
+ function formatTime(seconds) {
718
+ const mins = Math.floor(seconds / 60);
719
+ const secs = Math.floor(seconds % 60);
720
+ return `${mins}:${secs < 10 ? '0' : ''}${secs}`;
721
+ }
722
+
723
+ // 格式化文件大小
724
+ function formatFileSize(bytes) {
725
+ if (bytes === 0) return '0 Bytes';
726
+
727
+ const k = 1024;
728
+ const sizes = ['Bytes', 'KB', 'MB', 'GB'];
729
+ const i = Math.floor(Math.log(bytes) / Math.log(k));
730
+
731
+ return parseFloat((bytes / Math.pow(k, i)).toFixed(2)) + ' ' + sizes[i];
732
+ }
733
+
734
+ // 格式化日期
735
+ function formatDate(dateString) {
736
+ const date = new Date(dateString);
737
+ return date.toLocaleDateString();
738
+ }
739
+
740
  // 更新连接状态显示
741
  function updateConnectionStatus(connected, message, isConnecting = false) {
742
  const statusDot = connectionStatus.querySelector('span:first-child');