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}
`;
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}
`;
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 songItemElement = document.querySelector(`.song-item[data-index='${index}']`);
const playIconElement = songItemElement ? songItemElement.querySelector('.play-icon') : null;
if (playIconElement) {
playIconElement.className = 'loading-spinner';
}
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);
if (playIconElement) {
playIconElement.className = 'fas fa-play play-icon';
}
}).catch((error) => {
this.handleAudioError(_('playbackError'));
if (playIconElement) {
playIconElement.className = 'fas fa-play play-icon';
}
});
}
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}
`;
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();
}
}