| <!DOCTYPE html> |
| <html lang="zh-CN"> |
| <head> |
| <meta charset="UTF-8"> |
| <meta name="viewport" content="width=device-width, initial-scale=1.0"> |
| <title>WebDAV音乐播放器</title> |
| <script src="https://cdn.tailwindcss.com"></script> |
| <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css"> |
| <style> |
| .progress-container:hover .progress-bar { |
| height: 8px; |
| } |
| .progress-bar { |
| transition: all 0.3s ease; |
| } |
| .waveform { |
| display: flex; |
| align-items: flex-end; |
| height: 40px; |
| gap: 2px; |
| } |
| .wave-bar { |
| width: 3px; |
| background-color: #3b82f6; |
| border-radius: 3px; |
| transition: height 0.2s ease; |
| } |
| @keyframes spin { |
| 0% { transform: rotate(0deg); } |
| 100% { transform: rotate(360deg); } |
| } |
| .animate-spin { |
| animation: spin 2s linear infinite; |
| } |
| .album-art { |
| box-shadow: 0 10px 15px -3px rgba(0, 0, 0, 0.1), 0 4px 6px -2px rgba(0, 0, 0, 0.05); |
| } |
| .glow { |
| animation: glow 1.5s ease-in-out infinite alternate; |
| } |
| @keyframes glow { |
| from { |
| box-shadow: 0 0 5px rgba(59, 130, 246, 0.5); |
| } |
| to { |
| box-shadow: 0 0 20px rgba(59, 130, 246, 0.8); |
| } |
| } |
| .connection-pulse { |
| animation: pulse 2s infinite; |
| } |
| @keyframes pulse { |
| 0% { |
| opacity: 0.6; |
| } |
| 50% { |
| opacity: 1; |
| } |
| 100% { |
| opacity: 0.6; |
| } |
| } |
| .connection-error { |
| animation: shake 0.5s linear; |
| } |
| @keyframes shake { |
| 0%, 100% { transform: translateX(0); } |
| 10%, 30%, 50%, 70%, 90% { transform: translateX(-5px); } |
| 20%, 40%, 60%, 80% { transform: translateX(5px); } |
| } |
| .fade-in { |
| animation: fadeIn 0.5s ease-in; |
| } |
| @keyframes fadeIn { |
| from { opacity: 0; } |
| to { opacity: 1; } |
| } |
| </style> |
| </head> |
| <body class="bg-gray-100 text-gray-800 font-sans"> |
| <div class="container mx-auto max-w-4xl p-4"> |
| <div class="text-center mb-8"> |
| <h1 class="text-3xl font-bold text-blue-600 mb-2">WebDAV音乐播放器</h1> |
| <p class="text-gray-600">连接您的WebDAV服务器,随时随地享受音乐</p> |
| </div> |
| |
| |
| <div class="bg-white rounded-xl shadow-lg p-6 mb-6 border border-gray-200"> |
| <h2 class="text-xl font-semibold mb-4 flex items-center"> |
| <i class="fas fa-server mr-2 text-blue-500"></i> |
| <span>连接设置</span> |
| </h2> |
| <div class="grid grid-cols-1 md:grid-cols-4 gap-4 mb-4"> |
| <div class="md:col-span-2"> |
| <label class="block text-sm font-medium mb-1">服务器地址</label> |
| <div class="relative"> |
| <div class="absolute inset-y-0 left-0 pl-3 flex items-center pointer-events-none"> |
| <i class="fas fa-link text-gray-400"></i> |
| </div> |
| <input type="text" id="serverUrl" placeholder="https://example.com/webdav" |
| class="w-full pl-10 pr-3 py-2 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500"> |
| </div> |
| </div> |
| <div> |
| <label class="block text-sm font-medium mb-1">用户名</label> |
| <div class="relative"> |
| <div class="absolute inset-y-0 left-0 pl-3 flex items-center pointer-events-none"> |
| <i class="fas fa-user text-gray-400"></i> |
| </div> |
| <input type="text" id="username" placeholder="用户名" |
| class="w-full pl-10 pr-3 py-2 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500"> |
| </div> |
| </div> |
| <div> |
| <label class="block text-sm font-medium mb-1">密码</label> |
| <div class="relative"> |
| <div class="absolute inset-y-0 left-0 pl-3 flex items-center pointer-events-none"> |
| <i class="fas fa-lock text-gray-400"></i> |
| </div> |
| <input type="password" id="password" placeholder="密码" |
| class="w-full pl-10 pr-3 py-2 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500"> |
| </div> |
| </div> |
| </div> |
| <div class="flex justify-between items-center"> |
| <div id="connectionStatus" class="text-sm flex items-center"> |
| <span class="inline-block w-3 h-3 rounded-full bg-gray-400 mr-2"></span> |
| <span>未连接</span> |
| </div> |
| <button id="connectBtn" class="px-4 py-2 bg-blue-600 text-white rounded-md hover:bg-blue-700 focus:outline-none focus:ring-2 focus:ring-blue-500 focus:ring-offset-2 transition-colors duration-200 flex items-center"> |
| <i class="fas fa-plug mr-2"></i> |
| <span>连接</span> |
| </button> |
| </div> |
| </div> |
| |
| |
| <div id="connectionTestPanel" class="bg-blue-50 border-l-4 border-blue-500 text-blue-700 p-4 mb-6 rounded-lg hidden"> |
| <div class="flex items-center"> |
| <i class="fas fa-info-circle mr-2"></i> |
| <div> |
| <p class="font-medium">连接测试</p> |
| <p id="connectionTestMessage" class="text-sm">正在测试连接...</p> |
| </div> |
| </div> |
| </div> |
| |
| |
| <div id="errorPanel" class="bg-red-100 border-l-4 border-red-500 text-red-700 p-4 mb-6 rounded-lg hidden"> |
| <div class="flex items-center"> |
| <i class="fas fa-exclamation-circle mr-2"></i> |
| <div> |
| <p class="font-medium">连接错误</p> |
| <p id="errorMessage" class="text-sm"></p> |
| </div> |
| </div> |
| </div> |
| |
| |
| <div id="playerPanel" class="hidden"> |
| <div class="bg-white rounded-xl shadow-lg p-6 mb-6 border border-gray-200 fade-in"> |
| <div class="flex justify-between items-center mb-6"> |
| <h2 class="text-xl font-semibold flex items-center"> |
| <i class="fas fa-music mr-2 text-blue-500"></i> |
| <span>音乐库</span> |
| </h2> |
| <div class="flex items-center space-x-2"> |
| <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"> |
| <i class="fas fa-sync-alt mr-1"></i> |
| <span>刷新</span> |
| </button> |
| <div class="relative"> |
| <input type="text" id="searchInput" placeholder="搜索音乐..." |
| class="pl-8 pr-3 py-1 border border-gray-300 rounded-md focus:outline-none focus:ring-1 focus:ring-blue-500"> |
| <div class="absolute inset-y-0 left-0 pl-2 flex items-center pointer-events-none"> |
| <i class="fas fa-search text-gray-400"></i> |
| </div> |
| </div> |
| </div> |
| </div> |
| |
| <div id="loadingIndicator" class="text-center py-8 hidden"> |
| <div class="inline-block animate-spin rounded-full h-8 w-8 border-t-2 border-b-2 border-blue-500 mb-2"></div> |
| <p class="text-gray-600">正在扫描音乐文件...</p> |
| </div> |
| |
| <div id="musicList" class="space-y-2 max-h-96 overflow-y-auto"> |
| |
| <div class="text-center py-8 text-gray-500"> |
| <i class="fas fa-music fa-2x mb-2"></i> |
| <p>连接成功后,音乐将显示在这里</p> |
| </div> |
| </div> |
| |
| <div id="playerControls" class="mt-6 bg-gray-50 p-4 rounded-lg hidden"> |
| <div class="flex items-center space-x-4 mb-4"> |
| <div class="flex-shrink-0"> |
| <img id="currentAlbumArt" src="https://via.placeholder.com/80" alt="专辑封面" class="w-16 h-16 rounded-md album-art"> |
| </div> |
| <div class="flex-grow"> |
| <h3 id="currentSongTitle" class="font-medium">未选择歌曲</h3> |
| <p id="currentSongArtist" class="text-sm text-gray-600">未知艺术家</p> |
| </div> |
| <div class="flex-shrink-0"> |
| <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"> |
| <i class="fas fa-play"></i> |
| </button> |
| </div> |
| </div> |
| |
| <div class="progress-container mb-2"> |
| <div class="flex justify-between text-xs text-gray-500 mb-1"> |
| <span id="currentTime">0:00</span> |
| <span id="duration">0:00</span> |
| </div> |
| <div class="w-full bg-gray-200 rounded-full h-1.5 cursor-pointer"> |
| <div id="progressBar" class="bg-blue-600 h-1.5 rounded-full w-0"></div> |
| </div> |
| </div> |
| |
| <div class="flex justify-between items-center mt-4"> |
| <div class="flex items-center space-x-4"> |
| <button id="prevBtn" class="text-gray-700 hover:text-blue-600 transition-colors duration-200"> |
| <i class="fas fa-step-backward"></i> |
| </button> |
| <button id="nextBtn" class="text-gray-700 hover:text-blue-600 transition-colors duration-200"> |
| <i class="fas fa-step-forward"></i> |
| </button> |
| </div> |
| <div class="flex items-center space-x-2"> |
| <button id="volumeBtn" class="text-gray-700 hover:text-blue-600 transition-colors duration-200"> |
| <i class="fas fa-volume-up"></i> |
| </button> |
| <input type="range" id="volumeControl" min="0" max="1" step="0.01" value="0.7" |
| class="w-24 accent-blue-600 cursor-pointer"> |
| </div> |
| </div> |
| </div> |
| </div> |
| </div> |
| </div> |
|
|
| |
| <script src="https://cdn.jsdelivr.net/npm/webdav@4.11.0/dist/web/index.min.js"></script> |
| |
| <script> |
| |
| let webDAVClient = null; |
| let isConnected = false; |
| let retryCount = 0; |
| const MAX_RETRIES = 2; |
| let musicFiles = []; |
| let currentAudio = new Audio(); |
| let currentTrackIndex = -1; |
| let isPlaying = false; |
| |
| |
| const connectBtn = document.getElementById('connectBtn'); |
| const serverUrlInput = document.getElementById('serverUrl'); |
| const usernameInput = document.getElementById('username'); |
| const passwordInput = document.getElementById('password'); |
| const connectionStatus = document.getElementById('connectionStatus'); |
| const errorPanel = document.getElementById('errorPanel'); |
| const errorMessage = document.getElementById('errorMessage'); |
| const connectionTestPanel = document.getElementById('connectionTestPanel'); |
| const connectionTestMessage = document.getElementById('connectionTestMessage'); |
| const playerPanel = document.getElementById('playerPanel'); |
| const musicList = document.getElementById('musicList'); |
| const loadingIndicator = document.getElementById('loadingIndicator'); |
| const playerControls = document.getElementById('playerControls'); |
| const refreshBtn = document.getElementById('refreshBtn'); |
| const searchInput = document.getElementById('searchInput'); |
| const currentAlbumArt = document.getElementById('currentAlbumArt'); |
| const currentSongTitle = document.getElementById('currentSongTitle'); |
| const currentSongArtist = document.getElementById('currentSongArtist'); |
| const playPauseBtn = document.getElementById('playPauseBtn'); |
| const prevBtn = document.getElementById('prevBtn'); |
| const nextBtn = document.getElementById('nextBtn'); |
| const volumeBtn = document.getElementById('volumeBtn'); |
| const volumeControl = document.getElementById('volumeControl'); |
| const progressBar = document.getElementById('progressBar'); |
| const currentTime = document.getElementById('currentTime'); |
| const duration = document.getElementById('duration'); |
| |
| |
| document.addEventListener('DOMContentLoaded', () => { |
| |
| const savedConfig = localStorage.getItem('webdavPlayerConfig'); |
| if (savedConfig) { |
| const config = JSON.parse(savedConfig); |
| serverUrlInput.value = config.serverUrl || ''; |
| usernameInput.value = config.username || ''; |
| passwordInput.value = config.password || ''; |
| } |
| |
| |
| connectBtn.addEventListener('click', handleConnect); |
| refreshBtn.addEventListener('click', scanMusicFiles); |
| searchInput.addEventListener('input', filterMusicList); |
| playPauseBtn.addEventListener('click', togglePlayPause); |
| prevBtn.addEventListener('click', playPreviousTrack); |
| nextBtn.addEventListener('click', playNextTrack); |
| volumeControl.addEventListener('input', adjustVolume); |
| volumeBtn.addEventListener('click', toggleMute); |
| |
| |
| document.querySelector('.progress-container').addEventListener('click', (e) => { |
| if (!currentAudio.src) return; |
| |
| const progressContainer = e.currentTarget; |
| const clickPosition = e.clientX - progressContainer.getBoundingClientRect().left; |
| const containerWidth = progressContainer.clientWidth; |
| const seekPercentage = clickPosition / containerWidth; |
| const seekTime = seekPercentage * currentAudio.duration; |
| |
| currentAudio.currentTime = seekTime; |
| }); |
| |
| |
| currentAudio.addEventListener('timeupdate', updateProgressBar); |
| currentAudio.addEventListener('ended', playNextTrack); |
| currentAudio.addEventListener('loadedmetadata', updateDuration); |
| |
| |
| checkWebDAVLibrary(); |
| }); |
| |
| |
| function checkWebDAVLibrary() { |
| if (typeof webdav === 'undefined') { |
| showError('WebDAV客户端库加载失败,请刷新页面重试'); |
| connectionStatus.classList.add('connection-error'); |
| updateConnectionStatus(false, '库加载失败'); |
| } |
| } |
| |
| |
| async function handleConnect() { |
| const serverUrl = serverUrlInput.value.trim(); |
| const username = usernameInput.value.trim(); |
| const password = passwordInput.value; |
| |
| if (!serverUrl) { |
| showError('请输入服务器地址'); |
| connectionStatus.classList.add('connection-error'); |
| return; |
| } |
| |
| |
| try { |
| new URL(serverUrl); |
| } catch (e) { |
| showError('请输入有效的服务器地址 (例如: https://example.com/webdav)'); |
| connectionStatus.classList.add('connection-error'); |
| return; |
| } |
| |
| |
| localStorage.setItem('webdavPlayerConfig', JSON.stringify({ |
| serverUrl, |
| username, |
| password |
| })); |
| |
| |
| hideError(); |
| connectionStatus.classList.remove('connection-error'); |
| playerPanel.classList.add('hidden'); |
| connectBtn.disabled = true; |
| connectBtn.innerHTML = '<i class="fas fa-spinner animate-spin mr-2"></i>连接中...'; |
| |
| |
| connectionTestPanel.classList.remove('hidden'); |
| connectionTestMessage.textContent = '正在连接到服务器...'; |
| updateConnectionStatus(false, '连接中...', true); |
| |
| try { |
| |
| if (typeof webdav === 'undefined') { |
| throw new Error('WebDAV客户端库加载失败,请刷新页面重试'); |
| } |
| |
| |
| connectionTestMessage.textContent = '正在创建客户端连接...'; |
| webDAVClient = webdav.createClient(serverUrl, { |
| username, |
| password, |
| maxContentLength: 100 * 1024 * 1024 |
| }); |
| |
| |
| connectionTestMessage.textContent = '正在验证服务器响应...'; |
| const contents = await webDAVClient.getDirectoryContents('/'); |
| |
| |
| if (!contents || !Array.isArray(contents)) { |
| throw new Error('服务器响应无效,请检查权限'); |
| } |
| |
| |
| connectionTestMessage.textContent = '连接成功!正在加载音乐文件...'; |
| isConnected = true; |
| retryCount = 0; |
| updateConnectionStatus(true, '连接成功'); |
| |
| |
| setTimeout(() => { |
| connectionTestPanel.classList.add('hidden'); |
| }, 2000); |
| |
| |
| playerPanel.classList.remove('hidden'); |
| |
| |
| await scanMusicFiles(); |
| |
| } catch (error) { |
| console.error('连接失败:', error); |
| isConnected = false; |
| retryCount++; |
| |
| |
| let errorMsg = `连接失败: ${error.message}`; |
| |
| if (error.message.includes('NetworkError')) { |
| errorMsg = '网络错误,请检查服务器地址和网络连接'; |
| } else if (error.message.includes('401')) { |
| errorMsg = '认证失败,请检查用户名和密码'; |
| } else if (error.message.includes('404')) { |
| errorMsg = '服务器路径不存在,请检查WebDAV路径'; |
| } else if (error.message.includes('ECONNREFUSED')) { |
| errorMsg = '连接被拒绝,服务器可能未运行或端口错误'; |
| } |
| |
| connectionTestMessage.textContent = '连接失败'; |
| connectionStatus.classList.add('connection-error'); |
| updateConnectionStatus(false, '连接失败'); |
| showError(errorMsg); |
| playerPanel.classList.add('hidden'); |
| |
| |
| if (retryCount <= MAX_RETRIES) { |
| setTimeout(() => { |
| if (!isConnected) { |
| connectionTestMessage.textContent = `正在尝试重新连接 (${retryCount}/${MAX_RETRIES})...`; |
| handleConnect(); |
| } |
| }, 2000); |
| } |
| } finally { |
| connectBtn.disabled = false; |
| connectBtn.innerHTML = '<i class="fas fa-plug mr-2"></i>连接'; |
| |
| |
| if (!isConnected && retryCount >= MAX_RETRIES) { |
| setTimeout(() => { |
| connectionTestPanel.classList.add('hidden'); |
| }, 3000); |
| } |
| } |
| } |
| |
| |
| async function scanMusicFiles() { |
| if (!isConnected || !webDAVClient) { |
| showError('请先连接到WebDAV服务器'); |
| return; |
| } |
| |
| try { |
| |
| loadingIndicator.classList.remove('hidden'); |
| musicList.innerHTML = ''; |
| |
| |
| musicFiles = []; |
| await scanDirectory('/'); |
| |
| |
| loadingIndicator.classList.add('hidden'); |
| |
| if (musicFiles.length === 0) { |
| musicList.innerHTML = ` |
| <div class="text-center py-8 text-gray-500"> |
| <i class="fas fa-exclamation-circle fa-2x mb-2"></i> |
| <p>未找到音乐文件</p> |
| <p class="text-sm mt-2">支持的格式: .mp3, .ogg, .wav, .flac, .m4a</p> |
| </div> |
| `; |
| playerControls.classList.add('hidden'); |
| } else { |
| renderMusicList(); |
| playerControls.classList.remove('hidden'); |
| } |
| |
| } catch (error) { |
| console.error('扫描音乐文件失败:', error); |
| loadingIndicator.classList.add('hidden'); |
| showError(`扫描音乐文件失败: ${error.message}`); |
| } |
| } |
| |
| |
| async function scanDirectory(path) { |
| try { |
| const contents = await webDAVClient.getDirectoryContents(path); |
| |
| for (const item of contents) { |
| if (item.type === 'directory') { |
| |
| await scanDirectory(item.filename); |
| } else if (isMusicFile(item.filename)) { |
| |
| musicFiles.push({ |
| filename: item.filename, |
| basename: item.basename, |
| lastmod: item.lastmod, |
| size: item.size, |
| path: path |
| }); |
| } |
| } |
| } catch (error) { |
| console.error(`扫描目录 ${path} 失败:`, error); |
| throw error; |
| } |
| } |
| |
| |
| function isMusicFile(filename) { |
| const musicExtensions = ['.mp3', '.ogg', '.wav', '.flac', '.m4a']; |
| return musicExtensions.some(ext => filename.toLowerCase().endsWith(ext)); |
| } |
| |
| |
| function renderMusicList(filterText = '') { |
| musicList.innerHTML = ''; |
| |
| const filteredFiles = filterText |
| ? musicFiles.filter(file => |
| file.basename.toLowerCase().includes(filterText.toLowerCase())) |
| : musicFiles; |
| |
| if (filteredFiles.length === 0) { |
| musicList.innerHTML = ` |
| <div class="text-center py-8 text-gray-500"> |
| <i class="fas fa-search fa-2x mb-2"></i> |
| <p>没有找到匹配的音乐</p> |
| </div> |
| `; |
| return; |
| } |
| |
| filteredFiles.forEach((file, index) => { |
| const musicItem = document.createElement('div'); |
| musicItem.className = `flex items-center p-3 hover:bg-gray-50 rounded-md cursor-pointer transition-colors duration-200 ${currentTrackIndex === index ? 'bg-blue-50' : ''}`; |
| musicItem.innerHTML = ` |
| <div class="flex-shrink-0 w-10 h-10 bg-gray-200 rounded-md flex items-center justify-center text-gray-500"> |
| <i class="fas fa-music"></i> |
| </div> |
| <div class="ml-3 flex-grow"> |
| <h3 class="text-sm font-medium truncate">${file.basename}</h3> |
| <p class="text-xs text-gray-500">${formatFileSize(file.size)} • ${formatDate(file.lastmod)}</p> |
| </div> |
| <div class="ml-2 text-gray-400"> |
| <i class="fas fa-play"></i> |
| </div> |
| `; |
| |
| musicItem.addEventListener('click', () => playTrack(index)); |
| musicList.appendChild(musicItem); |
| }); |
| } |
| |
| |
| function filterMusicList() { |
| renderMusicList(searchInput.value.trim()); |
| } |
| |
| |
| async function playTrack(index) { |
| if (index < 0 || index >= musicFiles.length) return; |
| |
| currentTrackIndex = index; |
| const file = musicFiles[index]; |
| |
| try { |
| |
| renderMusicList(searchInput.value.trim()); |
| |
| |
| const fileUrl = await webDAVClient.getFileDownloadLink(file.filename); |
| |
| |
| currentAudio.src = fileUrl; |
| currentAudio.load(); |
| |
| |
| currentSongTitle.textContent = file.basename; |
| currentSongArtist.textContent = 'WebDAV音乐'; |
| currentAlbumArt.src = 'https://via.placeholder.com/80'; |
| |
| |
| currentAudio.play() |
| .then(() => { |
| isPlaying = true; |
| playPauseBtn.innerHTML = '<i class="fas fa-pause"></i>'; |
| }) |
| .catch(error => { |
| console.error('播放失败:', error); |
| showError('播放失败: ' + error.message); |
| }); |
| |
| } catch (error) { |
| console.error('获取音乐文件失败:', error); |
| showError('获取音乐文件失败: ' + error.message); |
| } |
| } |
| |
| |
| function togglePlayPause() { |
| if (!currentAudio.src) { |
| if (musicFiles.length > 0) { |
| playTrack(0); |
| } |
| return; |
| } |
| |
| if (isPlaying) { |
| currentAudio.pause(); |
| playPauseBtn.innerHTML = '<i class="fas fa-play"></i>'; |
| } else { |
| currentAudio.play() |
| .then(() => { |
| playPauseBtn.innerHTML = '<i class="fas fa-pause"></i>'; |
| }) |
| .catch(error => { |
| console.error('播放失败:', error); |
| showError('播放失败: ' + error.message); |
| }); |
| } |
| |
| isPlaying = !isPlaying; |
| } |
| |
| |
| function playPreviousTrack() { |
| if (musicFiles.length === 0) return; |
| |
| let newIndex = currentTrackIndex - 1; |
| if (newIndex < 0) { |
| newIndex = musicFiles.length - 1; |
| } |
| |
| playTrack(newIndex); |
| } |
| |
| |
| function playNextTrack() { |
| if (musicFiles.length === 0) return; |
| |
| let newIndex = currentTrackIndex + 1; |
| if (newIndex >= musicFiles.length) { |
| newIndex = 0; |
| } |
| |
| playTrack(newIndex); |
| } |
| |
| |
| function adjustVolume() { |
| currentAudio.volume = volumeControl.value; |
| |
| |
| if (currentAudio.volume === 0) { |
| volumeBtn.innerHTML = '<i class="fas fa-volume-mute"></i>'; |
| } else if (currentAudio.volume < 0.5) { |
| volumeBtn.innerHTML = '<i class="fas fa-volume-down"></i>'; |
| } else { |
| volumeBtn.innerHTML = '<i class="fas fa-volume-up"></i>'; |
| } |
| } |
| |
| |
| function toggleMute() { |
| if (currentAudio.volume === 0) { |
| |
| currentAudio.volume = volumeControl.value; |
| volumeBtn.innerHTML = currentAudio.volume < 0.5 |
| ? '<i class="fas fa-volume-down"></i>' |
| : '<i class="fas fa-volume-up"></i>'; |
| } else { |
| |
| volumeControl.value = currentAudio.volume; |
| currentAudio.volume = 0; |
| volumeBtn.innerHTML = '<i class="fas fa-volume-mute"></i>'; |
| } |
| } |
| |
| |
| function updateProgressBar() { |
| if (!isNaN(currentAudio.duration)) { |
| const progress = (currentAudio.currentTime / currentAudio.duration) * 100; |
| progressBar.style.width = `${progress}%`; |
| currentTime.textContent = formatTime(currentAudio.currentTime); |
| } |
| } |
| |
| |
| function updateDuration() { |
| if (!isNaN(currentAudio.duration)) { |
| duration.textContent = formatTime(currentAudio.duration); |
| } |
| } |
| |
| |
| function formatTime(seconds) { |
| const mins = Math.floor(seconds / 60); |
| const secs = Math.floor(seconds % 60); |
| return `${mins}:${secs < 10 ? '0' : ''}${secs}`; |
| } |
| |
| |
| function formatFileSize(bytes) { |
| if (bytes === 0) return '0 Bytes'; |
| |
| const k = 1024; |
| const sizes = ['Bytes', 'KB', 'MB', 'GB']; |
| const i = Math.floor(Math.log(bytes) / Math.log(k)); |
| |
| return parseFloat((bytes / Math.pow(k, i)).toFixed(2)) + ' ' + sizes[i]; |
| } |
| |
| |
| function formatDate(dateString) { |
| const date = new Date(dateString); |
| return date.toLocaleDateString(); |
| } |
| |
| |
| function updateConnectionStatus(connected, message, isConnecting = false) { |
| const statusDot = connectionStatus.querySelector('span:first-child'); |
| const statusText = connectionStatus.querySelector('span:last-child'); |
| |
| if (connected) { |
| statusDot.className = 'inline-block w-3 h-3 rounded-full bg-green-500 mr-2 glow'; |
| statusText.textContent = message; |
| } else if (isConnecting) { |
| statusDot.className = 'inline-block w-3 h-3 rounded-full bg-yellow-500 mr-2 connection-pulse'; |
| statusText.textContent = message; |
| } else { |
| statusDot.className = 'inline-block w-3 h-3 rounded-full bg-red-500 mr-2'; |
| statusText.textContent = message; |
| } |
| } |
| |
| |
| function showError(message) { |
| errorMessage.textContent = message; |
| errorPanel.classList.remove('hidden'); |
| } |
| |
| |
| function hideError() { |
| errorPanel.classList.add('hidden'); |
| } |
| </script> |
| <p style="border-radius: 8px; text-align: center; font-size: 12px; color: #fff; margin-top: 16px;position: fixed; left: 8px; bottom: 8px; z-index: 10; background: rgba(0, 0, 0, 0.8); padding: 4px 8px;">Made with <img src="https://enzostvs-deepsite.hf.space/logo.svg" alt="DeepSite Logo" style="width: 16px; height: 16px; vertical-align: middle;display:inline-block;margin-right:3px;filter:brightness(0) invert(1);"><a href="https://enzostvs-deepsite.hf.space" style="color: #fff;text-decoration: underline;" target="_blank" >DeepSite</a> - 🧬 <a href="https://enzostvs-deepsite.hf.space?remix=Finsqw/my" style="color: #fff;text-decoration: underline;" target="_blank" >Remix</a></p></body> |
| </html> |