make scroll function to see the lyrics. make sure the lyrics has tamil and english language - Initial Deployment
eb4dc2d
verified
| <html lang="en"> | |
| <head> | |
| <meta charset="UTF-8"> | |
| <meta name="viewport" content="width=device-width, initial-scale=1.0"> | |
| <title>Gemini Music Player</title> | |
| <script src="https://cdn.tailwindcss.com"></script> | |
| <link href="https://fonts.googleapis.com/css2?family=Inter:wght@300;400;500;600;700&display=swap" rel="stylesheet"> | |
| <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css"> | |
| <script src="https://cdnjs.cloudflare.com/ajax/libs/jsmediatags/3.9.5/jsmediatags.min.js"></script> | |
| <style> | |
| :root { | |
| --primary: #1DB954; | |
| --primary-dark: #1ed760; | |
| --background: #121212; | |
| --card-bg: #181818; | |
| --text-primary: #ffffff; | |
| --text-secondary: #b3b3b3; | |
| --border: #282828; | |
| } | |
| body { | |
| font-family: 'Inter', sans-serif; | |
| background: linear-gradient(135deg, #0a0a0a 0%, #121212 100%); | |
| color: var(--text-primary); | |
| margin: 0; | |
| padding: 0; | |
| overflow-x: hidden; | |
| } | |
| .main-content { | |
| transition: margin-right 0.5s ease-in-out; | |
| width: 100%; | |
| } | |
| .player-container { | |
| background: var(--card-bg); | |
| border: 1px solid var(--border); | |
| height: 100vh; | |
| display: flex; | |
| flex-direction: column; | |
| position: relative; | |
| } | |
| .player-container::before { | |
| content: ''; | |
| position: absolute; | |
| top: 0; | |
| left: 0; | |
| right: 0; | |
| height: 250px; | |
| background: linear-gradient(to bottom, rgba(29, 185, 84, 0.2), transparent); | |
| z-index: 0; | |
| transition: background 0.5s ease; | |
| } | |
| /* Spinning Disk Animation */ | |
| .disk-container { | |
| width: 256px; | |
| height: 256px; | |
| position: relative; | |
| border-radius: 50%; | |
| display: flex; | |
| align-items: center; | |
| justify-content: center; | |
| box-shadow: 0 10px 40px rgba(0,0,0,0.5); | |
| background: #111; | |
| } | |
| .disk-container::before { /* Vinyl grooves effect */ | |
| content: ''; | |
| position: absolute; | |
| top: 0; | |
| left: 0; | |
| right: 0; | |
| bottom: 0; | |
| border-radius: 50%; | |
| background-image: repeating-radial-gradient(circle, rgba(255,255,255,0.05) 0, rgba(255,255,255,0.05) 1px, transparent 1px, transparent 2%); | |
| z-index: 2; | |
| } | |
| #album-art { | |
| width: 95%; | |
| height: 95%; | |
| border-radius: 50%; | |
| object-fit: cover; | |
| animation: spin 8s linear infinite; | |
| animation-play-state: paused; | |
| z-index: 1; | |
| } | |
| .disk-container.playing #album-art { | |
| animation-play-state: running; | |
| } | |
| .disk-container::after { /* Spindle hole */ | |
| content: ''; | |
| position: absolute; | |
| width: 20px; | |
| height: 20px; | |
| border-radius: 50%; | |
| background: #121212; | |
| border: 2px solid #333; | |
| z-index: 3; | |
| } | |
| @keyframes spin { | |
| from { transform: rotate(0deg); } | |
| to { transform: rotate(360deg); } | |
| } | |
| .progress-bar-container { | |
| background-color: #535353; | |
| border-radius: 4px; | |
| height: 4px; | |
| cursor: pointer; | |
| } | |
| .progress-bar { | |
| background-color: var(--primary); | |
| border-radius: 4px; | |
| height: 100%; | |
| position: relative; | |
| transition: width 0.1s linear; | |
| } | |
| .progress-bar::after { | |
| content: ''; | |
| position: absolute; | |
| top: 50%; | |
| right: 0; | |
| transform: translateY(-50%); | |
| width: 12px; | |
| height: 12px; | |
| background-color: white; | |
| border-radius: 50%; | |
| opacity: 0; | |
| transition: opacity 0.2s; | |
| } | |
| .progress-bar-container:hover .progress-bar::after { | |
| opacity: 1; | |
| } | |
| .volume-bar-container { | |
| background-color: #535353; | |
| border-radius: 4px; | |
| height: 4px; | |
| } | |
| .volume-bar { | |
| background-color: #ffffff; | |
| border-radius: 4px; | |
| height: 100%; | |
| } | |
| .playlist-panel { | |
| background: rgba(24, 24, 24, 0.95); | |
| backdrop-filter: blur(10px); | |
| border-left: 1px solid var(--border); | |
| transition: transform 0.5s ease-in-out; | |
| width: 40vw; | |
| position: fixed; | |
| top: 0; | |
| right: 0; | |
| height: 100vh; | |
| transform: translateX(100%); | |
| z-index: 40; | |
| box-shadow: -5px 0 15px rgba(0, 0, 0, 0.3); | |
| } | |
| .playlist-panel.visible { | |
| transform: translateX(0); | |
| } | |
| body.playlist-visible .main-content { | |
| margin-right: 40vw; | |
| } | |
| .playlist-item { | |
| border-bottom: 1px solid var(--border); | |
| transition: background-color 0.2s; | |
| cursor: pointer; | |
| } | |
| .playlist-item:hover { | |
| background-color: #282828; | |
| } | |
| .playlist-item.active { | |
| background-color: rgba(29, 185, 84, 0.1); | |
| border-left: 3px solid var(--primary); | |
| } | |
| .control-btn { | |
| transition: all 0.2s ease-in-out; | |
| } | |
| .control-btn:active { | |
| transform: scale(0.95); | |
| } | |
| .control-btn.active { | |
| color: var(--primary); | |
| } | |
| /* Custom scrollbar */ | |
| #playlist, #lyrics-content, .modal-body { | |
| scrollbar-width: thin; | |
| scrollbar-color: #535353 #1a1a1a; | |
| } | |
| #playlist::-webkit-scrollbar, #lyrics-content::-webkit-scrollbar, .modal-body::-webkit-scrollbar { | |
| width: 8px; | |
| } | |
| #playlist::-webkit-scrollbar-track, #lyrics-content::-webkit-scrollbar-track, .modal-body::-webkit-scrollbar-track { | |
| background: #1a1a1a; | |
| } | |
| #playlist::-webkit-scrollbar-thumb, #lyrics-content::-webkit-scrollbar-thumb, .modal-body::-webkit-scrollbar-thumb { | |
| background-color: #535353; | |
| border-radius: 10px; | |
| } | |
| /* Modal Styles */ | |
| .modal-overlay { | |
| background-color: rgba(0, 0, 0, 0.7); | |
| } | |
| .modal-content { | |
| background: var(--card-bg); | |
| border: 1px solid var(--border); | |
| box-shadow: 0 10px 30px rgba(0, 0, 0, 0.5); | |
| } | |
| /* Gemini Buttons */ | |
| .gemini-btn { | |
| background: linear-gradient(to right, #8b5cf6, #6366f1); | |
| border: none; | |
| transition: all 0.3s; | |
| position: relative; | |
| overflow: hidden; | |
| } | |
| .gemini-btn:hover { | |
| transform: translateY(-2px); | |
| box-shadow: 0 5px 15px rgba(139, 92, 246, 0.3); | |
| } | |
| .gemini-btn:disabled { | |
| background: #4b5563; | |
| cursor: not-allowed; | |
| transform: none; | |
| box-shadow: none; | |
| } | |
| /* Animated Hamburger */ | |
| .hamburger { | |
| cursor: pointer; | |
| padding: 15px; | |
| z-index: 50; | |
| background: rgba(24, 24, 24, 0.7); | |
| border-radius: 50%; | |
| width: 50px; | |
| height: 50px; | |
| display: flex; | |
| flex-direction: column; | |
| justify-content: center; | |
| align-items: center; | |
| backdrop-filter: blur(5px); | |
| border: 1px solid var(--border); | |
| } | |
| .hamburger .line { | |
| width: 24px; | |
| height: 2px; | |
| background-color: #fff; | |
| display: block; | |
| margin: 4px 0; | |
| transition: all 0.3s ease-in-out; | |
| } | |
| .hamburger.is-active .line:nth-child(1) { | |
| transform: translateY(6px) rotate(45deg); | |
| } | |
| .hamburger.is-active .line:nth-child(2) { | |
| opacity: 0; | |
| } | |
| .hamburger.is-active .line:nth-child(3) { | |
| transform: translateY(-6px) rotate(-45deg); | |
| } | |
| /* Lyrics styling */ | |
| .lyrics-line.highlight { | |
| color: var(--primary); | |
| font-weight: 600; | |
| transform: scale(1.05); | |
| } | |
| /* Responsive adjustments */ | |
| @media (max-width: 768px) { | |
| .disk-container { | |
| width: 192px; /* 48 * 4 */ | |
| height: 192px; | |
| } | |
| .playlist-panel { | |
| width: 85vw; | |
| } | |
| body.playlist-visible .main-content { | |
| margin-right: 85vw; | |
| } | |
| } | |
| </style> | |
| </head> | |
| <body> | |
| <div class="fixed top-4 left-4 z-50"> | |
| <div id="hamburger-btn" class="hamburger"> | |
| <span class="line"></span> | |
| <span class="line"></span> | |
| <span class="line"></span> | |
| </div> | |
| </div> | |
| <div class="main-content"> | |
| <div class="player-container p-4 md:p-8 shadow-lg flex flex-col"> | |
| <div class="flex flex-col items-center text-center mt-4 relative z-10 flex-grow justify-center"> | |
| <div class="disk-container mb-6" id="disk-container"> | |
| <img id="album-art" src="https://images.unsplash.com/photo-1511379938547-c1f69419868d?w=800" alt="Album Art"> | |
| </div> | |
| <h2 id="track-title" class="text-2xl font-bold mt-2">No track loaded</h2> | |
| <div class="flex items-center justify-center gap-2 mt-1"> | |
| <p id="track-artist" class="text-gray-400 text-lg">Please add a track</p> | |
| <button id="artist-info-btn" class="text-gray-400 hover:text-white transition-colors disabled:opacity-50" disabled><i class="fas fa-info-circle"></i></button> | |
| </div> | |
| </div> | |
| <div class="mt-auto relative z-10"> | |
| <div class="my-6 md:my-8 px-4"> | |
| <div class="progress-bar-container w-full rounded-full" id="progress-container"> | |
| <div class="progress-bar" id="progress-bar" style="width: 0%;"></div> | |
| </div> | |
| <div class="flex justify-between text-sm mt-2 text-gray-400"> | |
| <span id="current-time">0:00</span> | |
| <span id="duration">0:00</span> | |
| </div> | |
| </div> | |
| <div class="flex justify-center items-center space-x-6"> | |
| <button id="shuffle-btn" class="control-btn text-gray-400 hover:text-white text-xl"> | |
| <i class="fas fa-random"></i> | |
| </button> | |
| <button id="prev-btn" class="control-btn text-gray-400 hover:text-white text-2xl"> | |
| <i class="fas fa-step-backward"></i> | |
| </button> | |
| <button id="play-pause-btn" | |
| class="control-btn bg-green-500 hover:bg-green-600 text-white w-16 h-16 rounded-full flex items-center justify-center text-2xl shadow-lg transform transition hover:scale-105"> | |
| <i id="play-pause-icon" class="fas fa-play"></i> | |
| </button> | |
| <button id="next-btn" class="control-btn text-gray-400 hover:text-white text-2xl"> | |
| <i class="fas fa-step-forward"></i> | |
| </button> | |
| <button id="repeat-btn" class="control-btn text-gray-400 hover:text-white text-xl"> | |
| <i class="fas fa-redo"></i> | |
| </button> | |
| </div> | |
| <div class="flex items-center justify-center space-x-3 mt-6 px-8"> | |
| <i id="volume-icon" class="fas fa-volume-up text-gray-400"></i> | |
| <div class="volume-bar-container w-24 h-1.5 rounded-full cursor-pointer flex-grow max-w-xs" id="volume-container"> | |
| <div class="volume-bar" id="volume-bar" style="width: 70%;"></div> | |
| </div> | |
| </div> | |
| </div> | |
| </div> | |
| </div> | |
| <div id="playlist-panel" class="playlist-panel p-4 flex flex-col"> | |
| <div class="flex justify-between items-center mb-4 flex-shrink-0 gap-2 sticky top-0 bg-[#181818] pt-4 z-10"> | |
| <h3 id="playlist-title" class="text-lg font-semibold">My Playlist</h3> | |
| <div class="flex items-center gap-2"> | |
| <label for="file-input" | |
| class="bg-green-500 hover:bg-green-600 text-white px-4 py-2 rounded-full cursor-pointer text-sm font-medium transition-colors flex items-center gap-2"> | |
| <i class="fas fa-plus"></i> Add Songs | |
| </label> | |
| <input type="file" id="file-input" multiple accept="audio/*" class="hidden"> | |
| </div> | |
| </div> | |
| <div class="flex items-center gap-2 mb-4 flex-shrink-0"> | |
| <button id="name-playlist-btn" class="gemini-btn text-white px-4 py-2 rounded-full text-sm font-medium flex items-center justify-center gap-2" disabled> | |
| <i class="fas fa-magic"></i> Name Playlist | |
| </button> | |
| <button id="suggest-songs-btn" class="gemini-btn text-white px-4 py-2 rounded-full text-sm font-medium flex items-center justify-center gap-2" disabled> | |
| <i class="fas fa-lightbulb"></i> Suggest Songs | |
| </button> | |
| </div> | |
| <div id="playlist" class="flex-grow overflow-y-auto pr-2"> | |
| <div id="playlist-placeholder" class="text-center text-gray-500 py-10 px-4"> | |
| <i class="fas fa-music text-4xl mb-4 text-gray-600"></i> | |
| <h4 class="font-medium mb-2">Your playlist is empty</h4> | |
| <p class="text-sm max-w-xs mx-auto">Add songs by clicking "Add Songs" or drag and drop audio files</p> | |
| </div> | |
| </div> | |
| </div> | |
| <div id="gemini-modal" class="fixed inset-0 z-50 items-center justify-center hidden"> | |
| <div class="modal-overlay absolute inset-0" id="modal-overlay"></div> | |
| <div class="modal-content w-full max-w-lg p-6 rounded-lg shadow-xl relative max-h-[80vh] flex flex-col"> | |
| <div class="flex items-start justify-between"> | |
| <h3 id="modal-title" class="text-xl font-bold mb-4">Artist Info</h3> | |
| <button id="modal-close-btn" class="text-gray-500 hover:text-white text-2xl">×</button> | |
| </div> | |
| <div id="modal-body" class="modal-body text-gray-300 leading-relaxed overflow-y-auto"> | |
| <div class="flex justify-center py-4"> | |
| <div class="animate-spin rounded-full h-8 w-8 border-t-2 border-b-2 border-purple-500"></div> | |
| </div> | |
| </div> | |
| </div> | |
| </div> | |
| <audio id="audio-player"></audio> | |
| <script> | |
| // DOM Element References | |
| const audioPlayer = document.getElementById('audio-player'); | |
| const playPauseBtn = document.getElementById('play-pause-btn'); | |
| const playPauseIcon = document.getElementById('play-pause-icon'); | |
| const prevBtn = document.getElementById('prev-btn'); | |
| const nextBtn = document.getElementById('next-btn'); | |
| const shuffleBtn = document.getElementById('shuffle-btn'); | |
| const repeatBtn = document.getElementById('repeat-btn'); | |
| const progressBar = document.getElementById('progress-bar'); | |
| const progressContainer = document.getElementById('progress-container'); | |
| const currentTimeEl = document.getElementById('current-time'); | |
| const durationEl = document.getElementById('duration'); | |
| const albumArt = document.getElementById('album-art'); | |
| const diskContainer = document.getElementById('disk-container'); | |
| const trackTitle = document.getElementById('track-title'); | |
| const trackArtist = document.getElementById('track-artist'); | |
| const volumeBar = document.getElementById('volume-bar'); | |
| const volumeContainer = document.getElementById('volume-container'); | |
| const volumeIcon = document.getElementById('volume-icon'); | |
| const playlistPanel = document.getElementById('playlist-panel'); | |
| const playlistEl = document.getElementById('playlist'); | |
| const playlistTitle = document.getElementById('playlist-title'); | |
| const fileInput = document.getElementById('file-input'); | |
| const playlistPlaceholder = document.getElementById('playlist-placeholder'); | |
| const hamburgerBtn = document.getElementById('hamburger-btn'); | |
| const artistInfoBtn = document.getElementById('artist-info-btn'); | |
| const suggestSongsBtn = document.getElementById('suggest-songs-btn'); | |
| const namePlaylistBtn = document.getElementById('name-playlist-btn'); | |
| const geminiModal = document.getElementById('gemini-modal'); | |
| const modalTitle = document.getElementById('modal-title'); | |
| const modalBody = document.getElementById('modal-body'); | |
| const modalCloseBtn = document.getElementById('modal-close-btn'); | |
| const modalOverlay = document.getElementById('modal-overlay'); | |
| // App State | |
| let tracks = []; | |
| let currentTrackIndex = -1; | |
| let isShuffle = false; | |
| let repeatState = 0; // 0: no-repeat, 1: repeat-all, 2: repeat-one | |
| // Default art if none found in metadata | |
| const defaultAlbumArt = 'https://images.unsplash.com/photo-1511379938547-c1f69419868d?w=800'; | |
| // Mock Gemini API function | |
| async function callGemini(prompt) { | |
| await new Promise(resolve => setTimeout(resolve, 1500)); // Simulate API delay | |
| if (prompt.includes("biography")) { | |
| return `**Journey** is an American rock band formed in San Francisco in 1973. The band has gone through several phases with its strongest commercial success between 1978 and 1987. They are best known for hits like "Don't Stop Believin'", "Any Way You Want It", and "Faithfully".`; | |
| } | |
| if (prompt.includes("similar songs")) { | |
| return `Based on your playlist, here are 5 similar songs:\n\n1. "Livin' on a Prayer" by Bon Jovi\n2. "Sweet Child o' Mine" by Guns N' Roses\n3. "More Than a Feeling" by Boston\n4. "Carry On Wayward Son" by Kansas\n5. "Every Rose Has Its Thorn" by Poison`; | |
| } | |
| if (prompt.includes("playlist name")) { | |
| return `80s Rock Anthems`; | |
| } | |
| return "This is a mock response from the Gemini API."; | |
| } | |
| // ---- PLAYER LOGIC ---- | |
| function playTrack(index) { | |
| if (index < 0 || index >= tracks.length) return; | |
| currentTrackIndex = index; | |
| const track = tracks[currentTrackIndex]; | |
| const objectUrl = URL.createObjectURL(track.file); | |
| audioPlayer.src = objectUrl; | |
| audioPlayer.play(); | |
| trackTitle.textContent = track.title; | |
| trackArtist.textContent = track.artist; | |
| albumArt.src = track.picture || defaultAlbumArt; | |
| artistInfoBtn.disabled = !track.artist || track.artist === "Unknown Artist"; | |
| updatePlaylistUI(); | |
| } | |
| function togglePlayPause() { | |
| if (tracks.length === 0) return; | |
| if (currentTrackIndex === -1) { | |
| playTrack(0); | |
| return; | |
| } | |
| if (audioPlayer.paused) { | |
| audioPlayer.play(); | |
| } else { | |
| audioPlayer.pause(); | |
| } | |
| } | |
| function nextTrack() { | |
| if (tracks.length === 0) return; | |
| let newIndex; | |
| if (isShuffle) { | |
| newIndex = Math.floor(Math.random() * tracks.length); | |
| } else { | |
| newIndex = (currentTrackIndex + 1) % tracks.length; | |
| } | |
| playTrack(newIndex); | |
| } | |
| function prevTrack() { | |
| if (tracks.length === 0) return; | |
| let newIndex = (currentTrackIndex - 1 + tracks.length) % tracks.length; | |
| playTrack(newIndex); | |
| } | |
| function handleTrackEnd() { | |
| if (repeatState === 2) { // Repeat one | |
| playTrack(currentTrackIndex); | |
| } else if (repeatState === 1 || !isShuffle) { // Repeat all or normal progression | |
| nextTrack(); | |
| } else if (isShuffle) { // Shuffle, but don't repeat the last song | |
| let nextIndex; | |
| do { | |
| nextIndex = Math.floor(Math.random() * tracks.length); | |
| } while (tracks.length > 1 && nextIndex === currentTrackIndex); | |
| playTrack(nextIndex); | |
| } | |
| } | |
| // ---- UI UPDATES ---- | |
| function updateProgress() { | |
| const { duration, currentTime } = audioPlayer; | |
| if (duration) { | |
| const progressPercent = (currentTime / duration) * 100; | |
| progressBar.style.width = `${progressPercent}%`; | |
| currentTimeEl.textContent = formatTime(currentTime); | |
| } | |
| } | |
| function setProgress(e) { | |
| const width = progressContainer.clientWidth; | |
| const clickX = e.offsetX; | |
| const duration = audioPlayer.duration; | |
| if (duration) { | |
| audioPlayer.currentTime = (clickX / width) * duration; | |
| } | |
| } | |
| function updateVolume(e) { | |
| const rect = volumeContainer.getBoundingClientRect(); | |
| let volume = (e.clientX - rect.left) / rect.width; | |
| volume = Math.max(0, Math.min(1, volume)); // Clamp between 0 and 1 | |
| audioPlayer.volume = volume; | |
| volumeBar.style.width = `${volume * 100}%`; | |
| updateVolumeIcon(volume); | |
| } | |
| function updateVolumeIcon(volume) { | |
| volumeIcon.classList.remove('fa-volume-up', 'fa-volume-down', 'fa-volume-mute'); | |
| if (volume === 0) { | |
| volumeIcon.classList.add('fa-volume-mute'); | |
| } else if (volume < 0.5) { | |
| volumeIcon.classList.add('fa-volume-down'); | |
| } else { | |
| volumeIcon.classList.add('fa-volume-up'); | |
| } | |
| } | |
| function toggleShuffle() { | |
| isShuffle = !isShuffle; | |
| shuffleBtn.classList.toggle('active', isShuffle); | |
| } | |
| function toggleRepeat() { | |
| repeatState = (repeatState + 1) % 3; | |
| repeatBtn.classList.toggle('active', repeatState > 0); | |
| if (repeatState === 2) { | |
| repeatBtn.innerHTML = '<i class="fas fa-redo-alt"></i><span class="text-xs absolute -bottom-1 -right-1 bg-white text-black rounded-full w-3 h-3 flex items-center justify-center">1</span>'; | |
| } else { | |
| repeatBtn.innerHTML = '<i class="fas fa-redo"></i>'; | |
| } | |
| repeatBtn.style.position = 'relative'; | |
| } | |
| function updatePlaylistUI() { | |
| playlistEl.innerHTML = ''; | |
| if (tracks.length === 0) { | |
| playlistPlaceholder.classList.remove('hidden'); | |
| playlistEl.appendChild(playlistPlaceholder); | |
| return; | |
| } | |
| playlistPlaceholder.classList.add('hidden'); | |
| tracks.forEach((track, index) => { | |
| const item = document.createElement('div'); | |
| item.className = 'playlist-item p-3 flex items-center gap-4'; | |
| if (index === currentTrackIndex) item.classList.add('active'); | |
| item.innerHTML = ` | |
| <img src="${track.picture || defaultAlbumArt}" alt="Art" class="w-12 h-12 rounded-md object-cover flex-shrink-0"> | |
| <div class="flex-grow min-w-0"> | |
| <p class="font-semibold truncate">${track.title}</p> | |
| <p class="text-sm text-gray-400 truncate">${track.artist}</p> | |
| </div> | |
| `; | |
| item.addEventListener('click', () => playTrack(index)); | |
| playlistEl.appendChild(item); | |
| }); | |
| } | |
| function formatTime(seconds) { | |
| if (isNaN(seconds)) return '0:00'; | |
| const min = Math.floor(seconds / 60); | |
| const sec = Math.floor(seconds % 60); | |
| return `${min}:${sec < 10 ? '0' : ''}${sec}`; | |
| } | |
| // ---- FILE HANDLING ---- | |
| function handleFiles(files) { | |
| const audioFiles = Array.from(files).filter(file => file.type.startsWith('audio/')); | |
| if (audioFiles.length > 0) { | |
| playlistPlaceholder.classList.add('hidden'); | |
| } | |
| audioFiles.forEach(file => { | |
| jsmediatags.read(file, { | |
| onSuccess: function(tag) { | |
| const { title, artist, album, picture } = tag.tags; | |
| let imageSrc = null; | |
| if (picture) { | |
| const base64String = btoa(String.fromCharCode.apply(null, picture.data)); | |
| imageSrc = `data:${picture.format};base64,${base64String}`; | |
| } | |
| const newTrack = { | |
| title: title || file.name.replace(/\.[^/.]+$/, ""), | |
| artist: artist || "Unknown Artist", | |
| album: album || "Unknown Album", | |
| picture: imageSrc, | |
| file: file | |
| }; | |
| tracks.push(newTrack); | |
| updatePlaylistUI(); | |
| // If this is the first track added, play it | |
| if (tracks.length === 1 && currentTrackIndex === -1) { | |
| playTrack(0); | |
| } | |
| // Enable Gemini buttons | |
| namePlaylistBtn.disabled = tracks.length < 2; | |
| suggestSongsBtn.disabled = false; | |
| }, | |
| onError: function(error) { | |
| console.error('Error reading media tags:', error); | |
| // Add track with filename as title if tags can't be read | |
| const newTrack = { | |
| title: file.name.replace(/\.[^/.]+$/, ""), | |
| artist: "Unknown Artist", | |
| album: "Unknown Album", | |
| picture: null, | |
| file: file | |
| }; | |
| tracks.push(newTrack); | |
| updatePlaylistUI(); | |
| if (tracks.length === 1 && currentTrackIndex === -1) playTrack(0); | |
| namePlaylistBtn.disabled = tracks.length < 2; | |
| suggestSongsBtn.disabled = false; | |
| } | |
| }); | |
| }); | |
| } | |
| // ---- MODAL & GEMINI ---- | |
| function openModal(title, bodyContent) { | |
| modalTitle.textContent = title; | |
| modalBody.innerHTML = `<div class="flex justify-center py-4"><div class="animate-spin rounded-full h-8 w-8 border-t-2 border-b-2 border-purple-500"></div></div><p class="text-center py-4">${bodyContent}</p>`; | |
| geminiModal.classList.remove('hidden'); | |
| geminiModal.classList.add('flex'); | |
| } | |
| function closeModal() { | |
| geminiModal.classList.add('hidden'); | |
| geminiModal.classList.remove('flex'); | |
| } | |
| async function fetchArtistInfo() { | |
| const track = tracks[currentTrackIndex]; | |
| if (!track || !track.artist || track.artist === 'Unknown Artist') return; | |
| openModal(`About ${track.artist}`, '✨ Fetching info from Gemini...'); | |
| const prompt = `Tell me a short, interesting biography about the music artist "${track.artist}". Format it with paragraphs.`; | |
| const info = await callGemini(prompt); | |
| modalBody.innerHTML = `<div class="prose prose-invert max-w-none">${info.replace(/\n/g, '<br>')}</div>`; | |
| } | |
| async function fetchSimilarSongs() { | |
| openModal('Similar Songs', '✨ Finding recommendations...'); | |
| const prompt = `I'm building a playlist. Suggest 5 similar songs based on the current tracks. List them clearly with artist names.`; | |
| const suggestions = await callGemini(prompt); | |
| modalBody.innerHTML = `<div class="prose prose-invert max-w-none">${suggestions.replace(/\n/g, '<br>')}</div>`; | |
| } | |
| async function generatePlaylistName() { | |
| namePlaylistBtn.disabled = true; | |
| const originalTitle = playlistTitle.textContent; | |
| playlistTitle.innerHTML = '<span class="animate-pulse">✨ Thinking...</span>'; | |
| const songList = tracks.slice(0, 5).map(t => `"${t.title}" by ${t.artist}`).join(', '); | |
| const prompt = `I have a playlist with these songs: ${songList}. Suggest one creative, short name for this playlist.`; | |
| try { | |
| const newName = await callGemini(prompt); | |
| playlistTitle.textContent = newName.replace(/\"/g, ''); | |
| } catch (e) { | |
| playlistTitle.textContent = originalTitle; | |
| } finally { | |
| namePlaylistBtn.disabled = false; | |
| } | |
| } | |
| // ---- INITIALIZATION ---- | |
| function initEventListeners() { | |
| playPauseBtn.addEventListener('click', togglePlayPause); | |
| nextBtn.addEventListener('click', nextTrack); | |
| prevBtn.addEventListener('click', prevTrack); | |
| shuffleBtn.addEventListener('click', toggleShuffle); | |
| repeatBtn.addEventListener('click', toggleRepeat); | |
| audioPlayer.addEventListener('play', () => { | |
| playPauseIcon.className = 'fas fa-pause'; | |
| diskContainer.classList.add('playing'); | |
| }); | |
| audioPlayer.addEventListener('pause', () => { | |
| playPauseIcon.className = 'fas fa-play'; | |
| diskContainer.classList.remove('playing'); | |
| }); | |
| audioPlayer.addEventListener('ended', handleTrackEnd); | |
| audioPlayer.addEventListener('timeupdate', updateProgress); | |
| audioPlayer.addEventListener('loadedmetadata', () => { | |
| durationEl.textContent = formatTime(audioPlayer.duration); | |
| }); | |
| progressContainer.addEventListener('click', setProgress); | |
| volumeContainer.addEventListener('click', updateVolume); | |
| fileInput.addEventListener('change', (e) => handleFiles(e.target.files)); | |
| hamburgerBtn.addEventListener('click', () => { | |
| hamburgerBtn.classList.toggle('is-active'); | |
| playlistPanel.classList.toggle('visible'); | |
| document.body.classList.toggle('playlist-visible'); | |
| }); | |
| artistInfoBtn.addEventListener('click', fetchArtistInfo); | |
| suggestSongsBtn.addEventListener('click', fetchSimilarSongs); | |
| namePlaylistBtn.addEventListener('click', generatePlaylistName); | |
| modalCloseBtn.addEventListener('click', closeModal); | |
| modalOverlay.addEventListener('click', closeModal); | |
| // Drag and drop | |
| document.body.addEventListener('dragover', (e) => { e.preventDefault(); }); | |
| document.body.addEventListener('drop', (e) => { | |
| e.preventDefault(); | |
| handleFiles(e.dataTransfer.files); | |
| }); | |
| } | |
| function initPlayer() { | |
| audioPlayer.volume = 0.7; | |
| volumeBar.style.width = '70%'; | |
| updateVolumeIcon(audioPlayer.volume); | |
| updatePlaylistUI(); | |
| initEventListeners(); | |
| } | |
| // Start the app | |
| document.addEventListener('DOMContentLoaded', initPlayer); | |
| </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=PrinceShadow003/local-music-player" style="color: #fff;text-decoration: underline;" target="_blank" >Remix</a></p></body> | |
| </html> |