🎵 Buscador de Música
🔑 Autenticación con API Key
// Configuración de la API
const API_CONFIG = {
deezer: {
baseUrl: 'https://api.deezer.com',
key: 'tu-api-key-aqui', // En producción usa variables de entorno
endpoints: {
search: '/search',
track: '/track/',
artist: '/artist/'
}
},
lastfm: {
baseUrl: 'https://ws.audioscrobbler.com/2.0/',
key: process.env.LASTFM_API_KEY,
format: 'json'
}
};
// Función de búsqueda con autenticación
async function searchMusic(query, api = 'deezer', limit = 20, index = 0) {
try {
// Construir URL con parámetros
const url = new URL(API_CONFIG[api].baseUrl + API_CONFIG[api].endpoints.search);
url.searchParams.append('q', query);
url.searchParams.append('limit', limit);
url.searchParams.append('index', index);
// Headers de autenticación
const headers = {
'Content-Type': 'application/json',
'Authorization': `Bearer ${API_CONFIG[api].key}`
};
// Realizar petición
const response = await fetch(url, {
method: 'GET',
headers: headers
});
// Verificar respuesta
if (!response.ok) {
throw new Error(`HTTP error! status: ${response.status}`);
}
// Parsear JSON
const data = await response.json();
return data;
} catch (error) {
console.error('Error en la búsqueda:', error);
throw error;
}
}
// Uso
searchMusic('metallica', 'deezer', 10)
.then(data => console.log('Resultados:', data))
.catch(error => console.error('Error:', error));
📄 Paginación de Resultados
class MusicAPIClient {
constructor(apiConfig) {
this.config = apiConfig;
this.currentPage = 0;
this.totalResults = 0;
this.resultsPerPage = 20;
}
async search(query, page = 0) {
this.currentPage = page;
const index = page * this.resultsPerPage;
try {
const response = await this.makeRequest(query, index);
this.totalResults = response.total || 0;
return {
data: response.data || response.results,
pagination: {
currentPage: page,
totalPages: Math.ceil(this.totalResults / this.resultsPerPage),
hasNext: index + this.resultsPerPage < this.totalResults,
hasPrev: page > 0,
totalResults: this.totalResults
}
};
} catch (error) {
throw new Error(`Search failed: ${error.message}`);
}
}
async makeRequest(query, index) {
const url = new URL(this.config.baseUrl + this.config.endpoints.search);
url.searchParams.append('q', query);
url.searchParams.append('limit', this.resultsPerPage);
url.searchParams.append('index', index);
const response = await fetch(url);
if (!response.ok) {
throw new Error(`HTTP ${response.status}: ${response.statusText}`);
}
return await response.json();
}
renderPagination(container, pagination, onPageChange) {
container.innerHTML = '';
// Botón anterior
if (pagination.hasPrev) {
const prevBtn = document.createElement('button');
prevBtn.className = 'page-btn';
prevBtn.textContent = '‹ Anterior';
prevBtn.onclick = () => onPageChange(this.currentPage - 1);
container.appendChild(prevBtn);
}
// Números de página
const startPage = Math.max(0, this.currentPage - 2);
const endPage = Math.min(pagination.totalPages - 1, this.currentPage + 2);
for (let i = startPage; i <= endPage; i++) {
const pageBtn = document.createElement('button');
pageBtn.className = `page-btn ${i === this.currentPage ? 'active' : ''}`;
pageBtn.textContent = i + 1;
pageBtn.onclick = () => onPageChange(i);
container.appendChild(pageBtn);
}
// Botón siguiente
if (pagination.hasNext) {
const nextBtn = document.createElement('button');
nextBtn.className = 'page-btn';
nextBtn.textContent = 'Siguiente ›';
nextBtn.onclick = () => onPageChange(this.currentPage + 1);
container.appendChild(nextBtn);
}
}
}
🎵 Reproductor de Música Vanilla JS
class MusicPlayer {
constructor() {
this.currentTrack = null;
this.audio = new Audio();
this.isPlaying = false;
this.playQueue = [];
this.currentIndex = 0;
}
loadTrack(trackUrl, trackInfo) {
this.currentTrack = trackInfo;
this.audio.src = trackUrl;
// Eventos del reproductor
this.audio.addEventListener('loadedmetadata', () => {
console.log('Track loaded:', trackInfo.title);
});
this.audio.addEventListener('ended', () => {
this.playNext();
});
this.audio.addEventListener('error', (e) => {
console.error('Error loading track:', e);
});
}
play() {
if (this.audio.src) {
this.audio.play()
.then(() => {
this.isPlaying = true;
this.updateUI();
})
.catch(error => {
console.error('Playback error:', error);
});
}
}
pause() {
this.audio.pause();
this.isPlaying = false;
this.updateUI();
}
stop() {
this.audio.pause();
this.audio.currentTime = 0;
this.isPlaying = false;
this.updateUI();
}
seek(time) {
if (this.audio.duration) {
this.audio.currentTime = (time / 100) * this.audio.duration;
}
}
setVolume(volume) {
this.audio.volume = Math.max(0, Math.min(1, volume));
}
playNext() {
if (this.currentIndex < this.playQueue.length - 1) {
this.currentIndex++;
const nextTrack = this.playQueue[this.currentIndex];
this.loadTrack(nextTrack.preview, nextTrack);
this.play();
}
}
playPrevious() {
if (this.currentIndex > 0) {
this.currentIndex--;
const prevTrack = this.playQueue[this.currentIndex];
this.loadTrack(prevTrack.preview, prevTrack);
this.play();
}
}
addToQueue(track) {
this.playQueue.push(track);
}
updateUI() {
// Actualizar botones de control
const playBtn = document.getElementById('playBtn');
const pauseBtn = document.getElementById('pauseBtn');
if (this.isPlaying) {
playBtn.style.display = 'none';
pauseBtn.style.display = 'inline-block';
} else {
playBtn.style.display = 'inline-block';
pauseBtn.style.display = 'none';
}
// Actualizar información de la canción
const trackInfo = document.getElementById('currentTrack');
if (this.currentTrack) {
trackInfo.innerHTML = `
${this.currentTrack.title}
${this.currentTrack.artist}
`;
}
}
}
// Uso
const player = new MusicPlayer();
// Eventos de UI
document.getElementById('playBtn').onclick = () => player.play();
document.getElementById('pauseBtn').onclick = () => player.pause();
document.getElementById('stopBtn').onclick = () => player.stop();
document.getElementById('nextBtn').onclick = () => player.playNext();
document.getElementById('prevBtn').onclick = () => player.playPrevious();
// Control de volumen
document.getElementById('volumeSlider').oninput = (e) => {
player.setVolume(e.target.value / 100);
};
🛡️ Manejo de Errores y Rate Limiting
class RobustAPIClient {
constructor(config) {
this.config = config;
this.retryAttempts = 3;
this.retryDelay = 1000; // 1 segundo
this.rateLimitDelay = 5000; // 5 segundos
}
async fetchWithRetry(url, options = {}, attempt = 1) {
try {
const response = await fetch(url, {
...options,
headers: {
'Content-Type': 'application/json',
...options.headers
}
});
// Manejar diferentes códigos de estado
if (response.status === 429) {
// Rate limiting
console.warn('Rate limited, waiting...');
await this.delay(this.rateLimitDelay);
return this.fetchWithRetry(url, options, attempt);
}
if (response.status >= 500) {
// Error del servidor, reintentar
if (attempt < this.retryAttempts) {
console.warn(`Server error, retrying attempt ${attempt + 1}`);
await this.delay(this.retryDelay * attempt);
return this.fetchWithRetry(url, options, attempt + 1);
}
}
if (!response.ok) {
// Otros errores HTTP
const errorData = await response.json().catch(() => ({}));
throw new APIError(
response.status,
response.statusText,
errorData
);
}
return await response.json();
} catch (error) {
if (error instanceof APIError) {
throw error;
}
// Errores de red u otros
if (attempt < this.retryAttempts) {
console.warn(`Network error, retrying attempt ${attempt + 1}`);
await this.delay(this.retryDelay * attempt);
return this.fetchWithRetry(url, options, attempt + 1);
}
throw new Error(`Failed after ${this.retryAttempts} attempts: ${error.message}`);
}
}
delay(ms) {
return new Promise(resolve => setTimeout(resolve, ms));
}
// Implementación de caché simple
async fetchWithCache(url, options = {}) {
const cacheKey = `${url}${JSON.stringify(options)}`;
const cached = sessionStorage.getItem(cacheKey);
if (cached) {
const { data, timestamp } = JSON.parse(cached);
// Cache por 5 minutos
if (Date.now() - timestamp < 300000) {
console.log('Returning cached data');
return data;
}
}
const data = await this.fetchWithRetry(url, options);
// Guardar en caché
sessionStorage.setItem(cacheKey, JSON.stringify({
data,
timestamp: Date.now()
}));
return data;
}
}
class APIError extends Error {
constructor(status, statusText, data) {
super(`API Error ${status}: ${statusText}`);
this.status = status;
this.statusText = statusText;
this.data = data;
}
}
← Volver a Tutoriales