import { getFromDB } from './db.js'; import { debounce, showNotification, _ } from './utils.js'; import { getMusicUrlsFromPlex } from './api.js'; export class MusicPlayer { constructor() { this.cancionesActuales = []; this.indiceActual = -1; this.isPlaying = false; this.audioPlayer = document.getElementById("audioPlayer"); this.currentArtist = ""; this.currentAlbumId = null; this.currentSongId = null; this.currentArtistId = null; this.currentSongArtistId = null; this.db = null; this.artistListScrollPosition = 0; this.artistsData = []; this.artistsPageSize = 20; this.currentPage = 0; this.shuffleMode = false; this.tokens = []; this.isPlayerVisible = false; this.isReady = false; this.isInitializing = false; } setDB(databaseInstance) { if (databaseInstance && this.db !== databaseInstance) { this.db = databaseInstance; if (!this.isReady && !this.isInitializing) { this.asyncInitialize(); } } } async asyncInitialize() { if (this.isReady || this.isInitializing || !this.db) return; this.isInitializing = true; try { this.setupEventHandlers(); await this.loadMusicData(); this.updateArtistCounter(); this.markCurrentArtist(); this.setupIndexedDBUpdateListener(); this.isReady = true; } catch (error) { showNotification(_('errorInitializingMusicPlayer'), "error"); this.isReady = false; } finally { this.isInitializing = false; } } async loadMusicData() { if (!this.db) { document.getElementById('artistList').innerHTML = `
${_('dbUnavailableError')}
`; this.artistsData = []; this.tokens = []; this.updateArtistCounter(); return; } try { const rawArtistData = await getFromDB('artists'); this.artistsData = rawArtistData || []; this.loadTokens(); const initialArtistList = this._generateFullArtistListForToken('all'); this.loadArtists(initialArtistList, this.currentPage); } catch (error) { showNotification(_('criticalErrorLoadingMusic'), "error"); this.artistsData = []; this.tokens = []; document.getElementById('artistList').innerHTML = `
${_('errorLoadingArtists')}
`; this.updateTokenSelectorUI(); this.updateArtistCounter(); throw error; } } setupEventHandlers() { document.querySelectorAll('#openMusicPlayerMobile, #openMusicPlayerDesktop, #openMusicPlayerFromMiniplayer').forEach(btn => { btn.addEventListener('click', () => this.togglePlayerVisibility()); }); document.getElementById('closeSideNavBtn').addEventListener('click', () => this.hidePlayer()); document.getElementById('searchArtist').addEventListener("input", debounce(() => this.filterArtists(), 300)); document.getElementById('searchSong').addEventListener("input", debounce(() => this.filterSongs(), 300)); document.getElementById('backBtn').addEventListener('click', () => this.showArtistList()); document.getElementById('playPauseBtn').addEventListener('click', () => this.togglePlayPause()); document.getElementById('nextBtn').addEventListener('click', () => this.playNext()); document.getElementById('prevBtn').addEventListener('click', () => this.playPrevious()); document.getElementById('shuffleBtn').addEventListener('click', () => this.toggleShuffle()); document.getElementById('volumeSlider').addEventListener("input", (event) => this.updateVolume(event)); document.getElementById('volume-icon-btn').addEventListener('click', (e) => { e.stopPropagation(); document.querySelector('.volume-slider-wrapper').classList.toggle('active'); this.audioPlayer.muted = false; if (this.audioPlayer.volume === 0) { this.audioPlayer.volume = 0.5; this.updateVolumeIcon(0.5); } }); document.addEventListener('click', (e) => { if (!document.getElementById('volumeControl').contains(e.target)) { document.querySelector('.volume-slider-wrapper').classList.remove('active'); } }); document.getElementById('downloadAlbumBtn').addEventListener('click', () => this.downloadAlbum()); document.getElementById('downloadBtn').addEventListener('click', () => { if (this.indiceActual >= 0 && this.cancionesActuales[this.indiceActual]) { const currentSong = this.cancionesActuales[this.indiceActual]; this.downloadSong(currentSong.url, currentSong.titulo, currentSong.extension); } }); this.audioPlayer.addEventListener("ended", () => this.handleAudioEnded()); this.audioPlayer.addEventListener("timeupdate", () => this.updateProgressBar()); this.audioPlayer.addEventListener("error", () => this.handleAudioErrorEvent()); const progressBarContainer = document.getElementById('progressBarContainer'); progressBarContainer.addEventListener('click', (event) => this.seek(event)); progressBarContainer.addEventListener('mousemove', (event) => this.updateSeekHover(event)); progressBarContainer.addEventListener('mouseleave', () => this.hideSeekHover()); document.getElementById('artistList').addEventListener("click", (event) => { const card = event.target.closest('.artist-card'); if(card) this.loadArtistSongs(card); }); document.getElementById('listaCanciones').addEventListener("click", (event) => { const item = event.target.closest('.song-item'); if(item) { const index = parseInt(item.dataset.index, 10); if (!isNaN(index) && this.cancionesActuales[index]) { this.playSong(this.cancionesActuales[index], index); } } }); document.getElementById('prevArtistsBtn').addEventListener('click', () => this.loadPreviousArtists()); document.getElementById('nextArtistsBtn').addEventListener('click', () => this.loadNextArtists()); const tokenSelectorContainer = document.getElementById('tokenSelectorContainer'); tokenSelectorContainer.addEventListener('click', e => { const target = e.target; const selectItems = tokenSelectorContainer.querySelector('.select-items'); if (target.closest('.select-selected')) { selectItems.classList.toggle('select-hide'); target.closest('.select-selected').classList.toggle('select-arrow-active'); } else if (target.classList.contains('select-option')) { const selectedText = tokenSelectorContainer.querySelector('.select-selected span'); selectedText.textContent = target.textContent; selectedText.dataset.value = target.dataset.value; selectItems.classList.add('select-hide'); tokenSelectorContainer.querySelector('.select-selected').classList.remove('select-arrow-active'); this.currentPage = 0; this.loadArtistsByToken(target.dataset.value); } }); document.addEventListener('click', (e) => { if (!tokenSelectorContainer.contains(e.target)) { tokenSelectorContainer.querySelector('.select-items').classList.add('select-hide'); tokenSelectorContainer.querySelector('.select-selected').classList.remove('select-arrow-active'); } }); document.getElementById('trackInfo').addEventListener('click', (event) => { if (event.target.closest('#albumCover')) { if (this.indiceActual >= 0 && this.cancionesActuales[this.indiceActual]) { this.showInfoModal(this.cancionesActuales[this.indiceActual]); } } }); } _getCurrentTokenFilter() { return document.querySelector("#tokenSelectorContainer .select-selected span").dataset.value; } handleAudioEnded() { this.playNext(); } handleAudioErrorEvent() { this.handleAudioError(_('playbackError')); } togglePlayerVisibility() { if (this.isPlayerVisible) this.hidePlayer(); else this.showPlayer(); } async showPlayer() { if (this.isPlayerVisible) return; gsap.to('#musicPlayerContainer', { x: 0, duration: 0.5, ease: 'power3.out' }); this.isPlayerVisible = true; if (!this.isReady) await this.asyncInitialize(); if (!this.isReady) { document.getElementById('artistList').innerHTML = `
${_('errorInitializingMusicPlayer')}
`; this.updateArtistCounter(); return; } if (this.artistsData.length === 0 && this.isReady) { try { await this.loadMusicData(); } catch (error) { document.getElementById('artistList').innerHTML = `
${_('errorLoadingArtists')}
`; this.updateArtistCounter(); } } this.markCurrentArtist(); this.markCurrentSong(); } hidePlayer() { if (!this.isPlayerVisible) return; gsap.to('#musicPlayerContainer', { x: '-100%', duration: 0.4, ease: 'power3.in' }); this.isPlayerVisible = false; } async handleDatabaseUpdate() { if (!this.isReady) await this.asyncInitialize(); if (!this.isReady) return; showNotification(_('updatingMusicData'), "info", 1500); const currentTokenFilter = this._getCurrentTokenFilter(); const songListPanel = document.getElementById("songListContainer"); const wasSongListVisible = songListPanel.style.visibility === 'visible' && songListPanel.style.opacity === '1'; const wasArtistId = this.currentArtistId; await this.loadMusicData(); this.loadArtistsByToken(currentTokenFilter); if (wasSongListVisible && wasArtistId) { const artistCard = document.querySelector(`.artist-card[data-id='${wasArtistId}']`); if (artistCard) { try { const canciones = await getMusicUrlsFromPlex(artistCard.dataset.token, artistCard.dataset.protocolo, artistCard.dataset.ip, artistCard.dataset.puerto, wasArtistId); this.handleSongsLoaded(canciones, wasArtistId); this.markCurrentSong(); const tl = gsap.timeline(); tl.set("#artistListContainer", { x: "-100%", autoAlpha: 0 }); tl.set("#songListContainer", { x: "0%", autoAlpha: 1 }); } catch (error) { this.showArtistList(); } } else this.showArtistList(); } else { this.loadArtistsByToken(currentTokenFilter); this.markCurrentArtist(); } showNotification(_('musicDataUpdated'), "success", 1500); } setupIndexedDBUpdateListener() { window.removeEventListener('indexedDBUpdated', this.boundHandleDatabaseUpdate); this.boundHandleDatabaseUpdate = this.handleDatabaseUpdate.bind(this); window.addEventListener('indexedDBUpdated', this.boundHandleDatabaseUpdate); } loadTokens() { const tokenMap = new Map(); (this.artistsData || []).forEach(servidor => { if (!servidor) return; const primaryToken = servidor.tokenPrincipal; const serverToken = servidor.token; const serverName = servidor.serverName || servidor.ip || `Token Desconocido`; if (primaryToken && !tokenMap.has(primaryToken)) tokenMap.set(primaryToken, serverName); else if (serverToken && !tokenMap.has(serverToken) && !primaryToken) tokenMap.set(serverToken, serverName); }); this.tokens = Array.from(tokenMap, ([value, name]) => ({ value, name })); this.updateTokenSelectorUI(); } updateTokenSelectorUI() { const selectItems = document.querySelector('#tokenSelectorContainer .select-items'); const selectedText = document.querySelector('#tokenSelectorContainer .select-selected span'); const previousValue = selectedText.dataset.value; selectItems.innerHTML = `
${_('musicAllServers')}
`; this.tokens.forEach(token => { const optionDiv = document.createElement('div'); optionDiv.classList.add('select-option'); optionDiv.dataset.value = token.value; optionDiv.textContent = token.name; selectItems.appendChild(optionDiv); }); const currentSelection = this.tokens.find(t => t.value === previousValue); if (currentSelection) { selectedText.textContent = currentSelection.name; selectedText.dataset.value = currentSelection.value; } else { selectedText.textContent = _('musicAllServers'); selectedText.dataset.value = 'all'; } } _generateFullArtistListForToken(token) { let filteredSourceData = (token === "all" || !token) ? this.artistsData : (this.artistsData || []).filter(s => s && (s.tokenPrincipal === token || (!s.tokenPrincipal && s.token === token))); const artistMap = new Map(); (filteredSourceData || []).forEach(servidor => { if (servidor && Array.isArray(servidor.titulos)) { servidor.titulos.forEach(artista => { if (artista && typeof artista.id !== 'undefined' && artista.id !== null && !artistMap.has(artista.id)) { artistMap.set(artista.id, { id: artista.id, title: artista.title || 'Artista Desconocido', token: servidor.token, ip: servidor.ip, puerto: servidor.puerto, protocolo: servidor.protocolo, serverName: servidor.serverName || servidor.ip || 'Servidor Desconocido', thumb: artista.thumb }); } }); } }); return Array.from(artistMap.values()).sort((a, b) => (a.title || '').localeCompare(b.title || '')); } loadArtists(fullArtistListForFilter, page) { if (!this.isReady) return; const totalArtists = fullArtistListForFilter.length; const start = page * this.artistsPageSize; const end = Math.min(start + this.artistsPageSize, totalArtists); const artistGrid = document.getElementById("artistList"); artistGrid.innerHTML = ''; if (totalArtists === 0) { artistGrid.innerHTML = `
${_('noArtistsFound')}
`; document.getElementById('prevArtistsBtn').style.display = 'none'; document.getElementById('nextArtistsBtn').style.display = 'none'; this.updateArtistCounter(0, 0, 0); return; } const artistsToDisplay = fullArtistListForFilter.slice(start, end); artistsToDisplay.forEach((artista) => { if (artista && typeof artista.id !== 'undefined' && artista.id !== null && artista.title) { const card = document.createElement('div'); card.className = 'artist-card'; const thumbUrl = artista.thumb ? `${artista.protocolo}://${artista.ip}:${artista.puerto}${artista.thumb}?X-Plex-Token=${artista.token}` : null; card.innerHTML = `
${thumbUrl ? `${artista.title}` : ''}
${artista.title}
`; card.dataset.id = artista.id; card.dataset.token = artista.token; card.dataset.ip = artista.ip; card.dataset.puerto = artista.puerto; card.dataset.protocolo = artista.protocolo; card.dataset.thumb = artista.thumb || ''; card.title = `${artista.title} en ${artista.serverName || 'Servidor Desconocido'}`; artistGrid.appendChild(card); } }); gsap.from(".artist-card", { duration: 0.5, opacity: 0, y: 20, stagger: 0.05, ease: "power3.out" }); document.getElementById('prevArtistsBtn').style.display = page > 0 ? 'inline-flex' : 'none'; document.getElementById('nextArtistsBtn').style.display = end < totalArtists ? 'inline-flex' : 'none'; this.markCurrentArtist(); this.updateArtistCounter(start + 1, end, totalArtists); } loadArtistsByToken(token) { if (!this.isReady) return; this.currentPage = 0; this.loadArtists(this._generateFullArtistListForToken(token), this.currentPage); } loadPreviousArtists() { if (!this.isReady || this.currentPage <= 0) return; this.currentPage--; this.loadArtists(this._generateFullArtistListForToken(this._getCurrentTokenFilter()), this.currentPage); } loadNextArtists() { if (!this.isReady) return; const fullList = this._generateFullArtistListForToken(this._getCurrentTokenFilter()); if ((this.currentPage + 1) * this.artistsPageSize < fullList.length) { this.currentPage++; this.loadArtists(fullList, this.currentPage); } } updateArtistCounter(start = 0, end = 0, total = 0) { const counter = document.getElementById('artistCounter'); if (!this.isReady) counter.textContent = _('artistsCounterLoading'); else if (total === 0) counter.textContent = _('artistsCounterSingle', '0'); else counter.textContent = _('artistsCounter', [start, end, total]); } async loadArtistSongs(selectedCard) { if (!this.isReady) return; const { token, protocolo, ip, puerto, id: artistaId, thumb } = selectedCard.dataset; this.currentArtist = selectedCard.querySelector('.artist-card-title').textContent; this.currentArtistId = artistaId; if (!artistaId) return; const thumbUrl = thumb ? `${protocolo}://${ip}:${puerto}${thumb}?X-Plex-Token=${token}` : 'img/no-profile.png'; document.getElementById('artist-header-thumb').src = thumbUrl; document.getElementById('artist-header-title').textContent = this.currentArtist; const tl = gsap.timeline(); tl.to("#artistListContainer", { x: "-100%", autoAlpha: 0, duration: 0.5, ease: "power3.inOut" }) .fromTo("#songListContainer", { x: "100%", autoAlpha: 0 }, { x: "0%", autoAlpha: 1, duration: 0.5, ease: "power3.inOut" }, "-=0.5"); document.getElementById('loader').style.display = 'block'; document.getElementById('listaCanciones').innerHTML = ''; try { let canciones = await getMusicUrlsFromPlex(token, protocolo, ip, puerto, artistaId); this.handleSongsLoaded(canciones, artistaId); } catch (error) { showNotification(_('errorFetchingArtistSongs'), "error"); document.getElementById('listaCanciones').innerHTML = `
${_('errorLoadingSongs')}
`; this.showArtistList(); } finally { document.getElementById('loader').style.display = 'none'; } } handleSongsLoaded(canciones, artistId) { if (!this.isReady) return; if (!Array.isArray(canciones)) canciones = []; if (canciones.length > 0) { if (!this.shuffleMode) { canciones.sort((a, b) => { const albumCompare = (a.album || '').localeCompare(b.album || ''); if (albumCompare !== 0) return albumCompare; const trackIndexA = a.trackIndex !== undefined ? a.trackIndex : (a.title || ''); const trackIndexB = b.trackIndex !== undefined ? b.trackIndex : (b.title || ''); return trackIndexA - trackIndexB; }); } else { this.shuffleArray(canciones); } } this.cancionesActuales = canciones; this.currentAlbumId = artistId; this.displaySongList(canciones); this.markCurrentSong(); this.markCurrentArtist(artistId); } shuffleArray(array) { for (let i = array.length - 1; i > 0; i--) { const j = Math.floor(Math.random() * (i + 1)); [array[i], array[j]] = [array[j], array[i]]; } } displaySongList(canciones) { if (!this.isReady) return; const lista = document.getElementById("listaCanciones"); lista.innerHTML = ''; if (!Array.isArray(canciones) || canciones.length === 0) { lista.innerHTML = `
${_('noSongsFound')}
`; return; } const albums = canciones.reduce((acc, cancion) => { const albumTitle = cancion.album || 'Otras Canciones'; if (!acc[albumTitle]) acc[albumTitle] = []; acc[albumTitle].push(cancion); return acc; }, {}); const fragment = document.createDocumentFragment(); for (const albumTitle in albums) { const albumWrapper = document.createElement('div'); albumWrapper.className = 'album-group'; const albumHeader = document.createElement('h6'); albumHeader.className = 'album-group-title'; albumHeader.textContent = albumTitle; albumWrapper.appendChild(albumHeader); albums[albumTitle].forEach(cancion => { const originalIndex = this.cancionesActuales.findIndex(c => c.id === cancion.id); if (cancion && cancion.titulo) { const item = document.createElement('div'); item.className = 'song-item'; item.innerHTML = ` ${cancion.index || originalIndex + 1}
${cancion.titulo}
`; item.dataset.index = originalIndex; item.dataset.id = cancion.id; item.dataset.artistId = cancion.artistId; item.title = `${cancion.titulo} - ${cancion.album}`; albumWrapper.appendChild(item); } }); fragment.appendChild(albumWrapper); } lista.appendChild(fragment); this.markCurrentSong(); gsap.from(".album-group", { duration: 0.5, opacity: 0, y: 20, stagger: 0.1, ease: "power3.out" }); } playSong(cancion, index) { if (!this.isReady || !this.audioPlayer || !cancion || !cancion.url) return; const miniplayer = document.getElementById('miniplayer'); if (miniplayer.style.display === 'none') { gsap.fromTo(miniplayer, { y: '100%' }, { display: 'grid', y: '0%', duration: 0.5, ease: 'power3.out' }); } document.body.classList.add('miniplayer-active'); this.audioPlayer.innerHTML = ''; const source = document.createElement('source'); source.src = cancion.url; source.type = this.getMimeType(cancion.extension); this.audioPlayer.appendChild(source); const tl = gsap.timeline(); tl.to(['#albumCover', '#trackTitle', '#trackArtist'], { opacity: 0, y: -10, duration: 0.2, ease: "power2.in", stagger: 0.05 }) .add(() => { document.getElementById('albumCover').src = cancion.cover || 'img/no-poster.png'; document.getElementById('trackTitle').textContent = cancion.titulo; document.getElementById('trackArtist').textContent = cancion.artista; }) .to(['#albumCover', '#trackTitle', '#trackArtist'], { opacity: 1, y: 0, duration: 0.4, ease: "power2.out", stagger: 0.07 }); this.audioPlayer.load(); this.audioPlayer.play().then(() => { this.isPlaying = true; document.getElementById('playPauseBtn').innerHTML = ''; this.indiceActual = index; this.currentSongId = cancion.id; this.currentSongArtistId = cancion.artistId; this.markCurrentSong(); this.markCurrentArtist(cancion.artistId); }).catch((error) => { this.handleAudioError(_('playbackError')); }); } getMimeType(extension) { const mimeTypes = { 'mp3': 'audio/mpeg', 'wav': 'audio/wav', 'ogg': 'audio/ogg', 'oga': 'audio/ogg', 'm4a': 'audio/mp4', 'mp4': 'audio/mp4', 'aac': 'audio/aac', 'flac': 'audio/flac', 'opus': 'audio/opus', 'weba': 'audio/webm', 'webm': 'audio/webm' }; return mimeTypes[extension?.toLowerCase()] || undefined; } togglePlayPause() { if (!this.isReady || !this.audioPlayer || this.indiceActual < 0) return; const btn = document.getElementById('playPauseBtn'); if (this.isPlaying) { this.audioPlayer.pause(); btn.innerHTML = ''; } else { this.audioPlayer.play().then(() => { btn.innerHTML = ''; }) .catch(err => { this.isPlaying = false; btn.innerHTML = ''; }); } this.isPlaying = !this.isPlaying; } playNext() { if (!this.isReady || this.cancionesActuales.length === 0) return; let nextIndex; if (this.shuffleMode) { if (this.cancionesActuales.length <= 1) { nextIndex = 0; } else { do { nextIndex = Math.floor(Math.random() * this.cancionesActuales.length); } while (nextIndex === this.indiceActual); } } else { nextIndex = (this.indiceActual + 1) % this.cancionesActuales.length; } if (this.cancionesActuales[nextIndex]) this.playSong(this.cancionesActuales[nextIndex], nextIndex); } playPrevious() { if (!this.isReady || this.cancionesActuales.length === 0) return; let prevIndex; if (this.shuffleMode) { if (this.cancionesActuales.length <= 1) { prevIndex = 0; } else { do { prevIndex = Math.floor(Math.random() * this.cancionesActuales.length); } while (prevIndex === this.indiceActual); } } else { prevIndex = (this.indiceActual - 1 + this.cancionesActuales.length) % this.cancionesActuales.length; } if (this.cancionesActuales[prevIndex]) this.playSong(this.cancionesActuales[prevIndex], prevIndex); } toggleShuffle() { if (!this.isReady) return; this.shuffleMode = !this.shuffleMode; document.getElementById('shuffleBtn').classList.toggle("active", this.shuffleMode); showNotification(this.shuffleMode ? _('shuffleOn') : _('shuffleOff'), 'info', 1500); } markCurrentSong() { if (!this.isReady) return; document.querySelectorAll(".song-item").forEach(item => { item.classList.remove("current-song"); }); if (this.currentSongId !== null) { const songItem = document.querySelector(`.song-item[data-id='${this.currentSongId}']`); if (songItem) { songItem.classList.add("current-song"); } } } markCurrentArtist(artistIdToMark = null) { if (!this.isReady) return; const targetArtistId = artistIdToMark !== null ? artistIdToMark : this.currentArtistId; document.querySelectorAll(".artist-card").forEach(card => card.classList.remove("current-artist")); if (targetArtistId !== null) { const artistCard = document.querySelector(`.artist-card[data-id='${targetArtistId}']`); if (artistCard) artistCard.classList.add("current-artist"); } } updateProgressBar() { if (!this.isReady || !this.audioPlayer) return; const { currentTime, duration } = this.audioPlayer; if (!isNaN(duration) && duration > 0) { const progressPercent = (currentTime / duration) * 100; document.getElementById("played-bar").style.width = `${progressPercent}%`; document.getElementById("progress-handle").style.left = `${progressPercent}%`; document.getElementById("elapsedTime").textContent = this.formatTime(currentTime); document.getElementById("remainingTime").textContent = this.formatTime(duration); } } updateSeekHover(event) { const progressBarContainer = document.getElementById("progressBarContainer"); const rect = progressBarContainer.getBoundingClientRect(); const hoverWidth = ((event.clientX - rect.left) / rect.width) * 100; document.getElementById('seek-hover-bar').style.width = `${Math.max(0, Math.min(hoverWidth, 100))}%`; } hideSeekHover() { document.getElementById('seek-hover-bar').style.width = `0%`; } handleAudioError(message = _('playbackError')) { if (!this.isReady || !this.audioPlayer) return; showNotification(message, "error"); document.getElementById("remainingTime").textContent = _('errorLabel'); document.getElementById('playPauseBtn').innerHTML = ''; this.isPlaying = false; } seek(event) { if (!this.isReady || !this.audioPlayer || isNaN(this.audioPlayer.duration) || this.audioPlayer.duration <= 0) return; const progressBarContainer = document.getElementById("progressBarContainer"); const rect = progressBarContainer.getBoundingClientRect(); const seekTime = ((event.clientX - rect.left) / rect.width) * this.audioPlayer.duration; this.audioPlayer.currentTime = Math.max(0, Math.min(seekTime, this.audioPlayer.duration)); } formatTime(seconds) { if (isNaN(seconds) || seconds < 0) return "0:00"; const minutes = Math.floor(seconds / 60); const remainingSeconds = Math.floor(seconds % 60); return `${minutes}:${remainingSeconds < 10 ? "0" : ""}${remainingSeconds}`; } downloadSong(url, titulo, extension) { if (!this.isReady || !url || !titulo) return; showNotification(_('downloadingSong', titulo), "info"); fetch(url).then(response => response.blob()).then(blob => { const urlBlob = window.URL.createObjectURL(blob); const a = document.createElement("a"); a.style.display = "none"; a.href = urlBlob; a.download = `${titulo.replace(/[\\/:*?"<>|]/g, "_").replace(/\s+/g, '_')}.${extension || 'mp3'}`; document.body.appendChild(a); a.click(); window.URL.revokeObjectURL(urlBlob); a.remove(); showNotification(_('songDownloaded', titulo), "success"); }).catch(err => showNotification(_('errorDownloadingSong', titulo), "error")); } downloadAlbum() { if (!this.isReady || this.cancionesActuales.length === 0 || !this.currentArtist) return; showNotification(_('generatingAlbumM3U', this.currentArtist), "info"); const m3uContent = ["#EXTM3U", ...this.cancionesActuales.map(c => `#EXTINF:-1 tvg-name="${c.artista} - ${c.titulo}",${c.artista} - ${c.titulo}\n${c.url}`)]; const blob = new Blob([m3uContent.join("\n")], { type: "audio/x-mpegurl;charset=utf-8" }); const urlBlob = window.URL.createObjectURL(blob); const a = document.createElement("a"); a.href = urlBlob; a.download = `${this.currentArtist.replace(/[\\/:*?"<>|]/g, "_").replace(/\s+/g, '_')}_Album.m3u`; document.body.appendChild(a); a.click(); window.URL.revokeObjectURL(urlBlob); a.remove(); showNotification(_('albumM3UGenerated', this.currentArtist), "success"); } updateVolume(event) { if (!this.isReady || !this.audioPlayer) return; const volume = event.currentTarget.value; this.audioPlayer.volume = volume; this.updateVolumeIcon(volume); } updateVolumeIcon(volume) { const vol = parseFloat(volume); const icon = document.querySelector('#volume-icon-btn i'); if (vol === 0) { icon.className = 'fas fa-volume-mute'; } else if (vol < 0.5) { icon.className = 'fas fa-volume-down'; } else { icon.className = 'fas fa-volume-up'; } } filterArtists() { if (!this.isReady) return; const value = document.getElementById("searchArtist").value.toLowerCase(); const tokenFilter = this._getCurrentTokenFilter(); const fullList = this._generateFullArtistListForToken(tokenFilter); const filteredArtists = fullList.filter(a => a.title?.toLowerCase().includes(value)); const artistGrid = document.getElementById("artistList"); artistGrid.innerHTML = ''; if (filteredArtists.length === 0) { artistGrid.innerHTML = `
${_('noArtistsFound')}
`; } else { filteredArtists.forEach((artista) => { const card = document.createElement('div'); card.className = 'artist-card'; const thumbUrl = artista.thumb ? `${artista.protocolo}://${artista.ip}:${artista.puerto}${artista.thumb}?X-Plex-Token=${artista.token}` : null; card.innerHTML = `
${thumbUrl ? `${artista.title}` : ''}
${artista.title}
`; card.dataset.id = artista.id; card.dataset.token = artista.token; card.dataset.ip = artista.ip; card.dataset.puerto = artista.puerto; card.dataset.protocolo = artista.protocolo; card.dataset.thumb = artista.thumb || ''; card.title = `${artista.title} en ${artista.serverName || 'Servidor Desconocido'}`; artistGrid.appendChild(card); }); } if (value === '') { this.loadArtists(fullList, this.currentPage); } else { document.getElementById('prevArtistsBtn').style.display = 'none'; document.getElementById('nextArtistsBtn').style.display = 'none'; document.getElementById('artistCounter').style.display = 'none'; this.markCurrentArtist(); } } filterSongs() { if (!this.isReady) return; const value = document.getElementById("searchSong").value.toLowerCase(); const filteredSongs = this.cancionesActuales.filter(c => c.titulo?.toLowerCase().includes(value) || c.album?.toLowerCase().includes(value)); this.displaySongList(filteredSongs); } showArtistList() { if (!this.isReady) return; const tl = gsap.timeline(); tl.to("#songListContainer", { x: "100%", autoAlpha: 0, duration: 0.5, ease: "power3.inOut" }) .to("#artistListContainer", { x: "0%", autoAlpha: 1, duration: 0.5, ease: "power3.inOut" }, "-=0.5"); if (document.getElementById("searchArtist").value === '') { this.loadArtists(this._generateFullArtistListForToken(this._getCurrentTokenFilter()), this.currentPage); } else { this.filterArtists(); } this.markCurrentArtist(); } showInfoModal(cancion) { if (!this.isReady || !cancion) return; document.querySelector('#infoModalLabel').textContent = `${_('infoModalTitle')}: ${cancion.titulo}`; document.querySelector('#modalTitle span').textContent = cancion.titulo || 'N/A'; document.querySelector('#modalArtist span').textContent = cancion.artista || 'N/A'; document.querySelector('#modalAlbum span').textContent = cancion.album || 'N/A'; document.querySelector('#modalSong span').textContent = cancion.titulo || 'N/A'; document.querySelector('#modalYear span').textContent = cancion.year || 'N/A'; document.querySelector('#modalGenre span').textContent = cancion.genre || 'N/A'; const infoModal = bootstrap.Modal.getOrCreateInstance(document.getElementById('infoModal')); infoModal.show(); } }