/** * ============================================ * AUDIOHM WEB PLAYER - OPTIMIZED * Version: 2.0 * Last Updated: 2026-01-19 * ============================================ */ // ============================================ // STATE MANAGEMENT // ============================================ const AppState = { isAuthenticated: false, currentPage: 'home', currentTrack: null, isPlaying: false, isShuffle: false, repeatMode: 'none', // none, one, all volume: 100, isMuted: false, likedTracks: new Set(), playlists: [], queue: [] }; // ============================================ // DOM ELEMENTS // ============================================ const DOM = { // Screens loadingScreen: null, loginScreen: null, mainApp: null, // Forms loginForm: null, registerForm: null, authError: null, // Navigation sidebar: null, navItems: null, mobileMenuBtn: null, logoutBtn: null, // Pages pages: {}, // Player audioPlayer: null, playBtn: null, prevBtn: null, nextBtn: null, shuffleBtn: null, repeatBtn: null, progressBar: null, volumeBar: null, muteBtn: null, likeBtn: null, playerCover: null, playerTitle: null, playerArtist: null, currentTime: null, totalTime: null, // Toast toastContainer: null }; // ============================================ // INITIALIZATION // ============================================ function init() { // Cache DOM elements cacheDOM(); // Check authentication checkAuth(); // Setup event listeners setupEventListeners(); // Hide loading screen hideLoadingScreen(); } function cacheDOM() { DOM.loadingScreen = document.getElementById('loading-screen'); DOM.loginScreen = document.getElementById('login-screen'); DOM.mainApp = document.getElementById('main-app'); DOM.loginForm = document.getElementById('login-form'); DOM.registerForm = document.getElementById('register-form'); DOM.authError = document.getElementById('auth-error'); DOM.sidebar = document.getElementById('sidebar'); DOM.navItems = document.querySelectorAll('.nav-item'); DOM.mobileMenuBtn = document.getElementById('mobile-menu-btn'); DOM.logoutBtn = document.getElementById('logout-btn'); ['home', 'search', 'library'].forEach(page => { DOM.pages[page] = document.getElementById(`${page}-page`); }); DOM.audioPlayer = document.getElementById('audio-player'); DOM.playBtn = document.getElementById('play-btn'); DOM.prevBtn = document.getElementById('prev-btn'); DOM.nextBtn = document.getElementById('next-btn'); DOM.shuffleBtn = document.getElementById('shuffle-btn'); DOM.repeatBtn = document.getElementById('repeat-btn'); DOM.progressBar = document.getElementById('progress-bar'); DOM.volumeBar = document.getElementById('volume-bar'); DOM.muteBtn = document.getElementById('mute-btn'); DOM.likeBtn = document.getElementById('like-btn'); DOM.playerCover = document.getElementById('player-cover'); DOM.playerTitle = document.getElementById('player-title'); DOM.playerArtist = document.getElementById('player-artist'); DOM.currentTime = document.getElementById('current-time'); DOM.totalTime = document.getElementById('total-time'); DOM.toastContainer = document.getElementById('toast-container'); } // ============================================ // EVENT LISTENERS // ============================================ function setupEventListeners() { // Auth forms if (DOM.loginForm) { DOM.loginForm.addEventListener('submit', handleLogin); } if (DOM.registerForm) { DOM.registerForm.addEventListener('submit', handleRegister); } // Show/hide register forms const showRegister = document.getElementById('show-register'); const showLogin = document.getElementById('show-login'); if (showRegister) { showRegister.addEventListener('click', (e) => { e.preventDefault(); DOM.loginForm.classList.add('hidden'); DOM.registerForm.classList.remove('hidden'); }); } if (showLogin) { showLogin.addEventListener('click', (e) => { e.preventDefault(); DOM.registerForm.classList.add('hidden'); DOM.loginForm.classList.remove('hidden'); }); } // Navigation DOM.navItems.forEach(item => { item.addEventListener('click', (e) => { e.preventDefault(); const page = item.dataset.page; navigateTo(page); }); }); // Mobile menu if (DOM.mobileMenuBtn) { DOM.mobileMenuBtn.addEventListener('click', toggleMobileMenu); } // Logout if (DOM.logoutBtn) { DOM.logoutBtn.addEventListener('click', handleLogout); } // Player controls setupPlayerControls(); } function setupPlayerControls() { // Play/Pause if (DOM.playBtn) { DOM.playBtn.addEventListener('click', togglePlayPause); } // Previous/Next if (DOM.prevBtn) { DOM.prevBtn.addEventListener('click', playPrevious); } if (DOM.nextBtn) { DOM.nextBtn.addEventListener('click', playNext); } // Shuffle if (DOM.shuffleBtn) { DOM.shuffleBtn.addEventListener('click', toggleShuffle); } // Repeat if (DOM.repeatBtn) { DOM.repeatBtn.addEventListener('click', toggleRepeat); } // Progress bar if (DOM.progressBar) { DOM.progressBar.addEventListener('input', handleSeek); } // Volume if (DOM.volumeBar) { DOM.volumeBar.addEventListener('input', handleVolumeChange); } // Mute if (DOM.muteBtn) { DOM.muteBtn.addEventListener('click', toggleMute); } // Like if (DOM.likeBtn) { DOM.likeBtn.addEventListener('click', toggleLike); } // Audio events if (DOM.audioPlayer) { DOM.audioPlayer.addEventListener('timeupdate', updateProgress); DOM.audioPlayer.addEventListener('loadedmetadata', updateDuration); DOM.audioPlayer.addEventListener('ended', handleTrackEnd); } } // ============================================ // AUTHENTICATION // ============================================ async function checkAuth() { const token = localStorage.getItem('token'); if (!token) { showScreen('login'); return; } try { const response = await fetch('/api/v1/auth/me', { headers: { 'Authorization': `Bearer ${token}` } }); if (response.ok) { AppState.isAuthenticated = true; showScreen('main'); loadUserData(); } else { localStorage.removeItem('token'); showScreen('login'); } } catch (error) { console.error('Auth check failed:', error); showScreen('login'); } } async function handleLogin(e) { e.preventDefault(); const email = document.getElementById('login-email').value; const password = document.getElementById('login-password').value; try { const response = await fetch('/api/v1/auth/login', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ email, password }) }); const data = await response.json(); if (response.ok) { localStorage.setItem('token', data.access_token); AppState.isAuthenticated = true; showScreen('main'); showToast('Connexion réussie!', 'success'); } else { showError(data.detail || 'Email ou mot de passe incorrect'); } } catch (error) { console.error('Login failed:', error); showError('Erreur de connexion'); } } async function handleRegister(e) { e.preventDefault(); const username = document.getElementById('register-username').value; const email = document.getElementById('register-email').value; const password = document.getElementById('register-password').value; try { const response = await fetch('/api/v1/auth/register', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ username, email, password }) }); const data = await response.json(); if (response.ok) { localStorage.setItem('token', data.access_token); AppState.isAuthenticated = true; showScreen('main'); showToast('Compte créé avec succès!', 'success'); } else { showError(data.detail || 'Erreur lors de la création du compte'); } } catch (error) { console.error('Register failed:', error); showError('Erreur de connexion'); } } function handleLogout() { localStorage.removeItem('token'); AppState.isAuthenticated = false; showScreen('login'); showToast('Déconnexion réussie', 'success'); } // ============================================ // NAVIGATION // ============================================ function navigateTo(page) { // Update active nav item DOM.navItems.forEach(item => { item.classList.remove('active'); if (item.dataset.page === page) { item.classList.add('active'); } }); // Show/hide pages Object.keys(DOM.pages).forEach(key => { if (key === page) { DOM.pages[key].classList.add('active'); } else { DOM.pages[key].classList.remove('active'); } }); AppState.currentPage = page; // Close mobile menu DOM.sidebar.classList.remove('open'); } function toggleMobileMenu() { DOM.sidebar.classList.toggle('open'); } // Close menu when clicking outside document.addEventListener('click', (e) => { if (!DOM.sidebar?.contains(e.target) && !DOM.mobileMenuBtn?.contains(e.target)) { DOM.sidebar?.classList.remove('open'); } }); // ============================================ // PLAYER CONTROLS // ============================================ function togglePlayPause() { if (!DOM.audioPlayer) return; if (DOM.audioPlayer.paused) { DOM.audioPlayer.play(); updatePlayButton(true); } else { DOM.audioPlayer.pause(); updatePlayButton(false); } } function updatePlayButton(isPlaying) { const icon = DOM.playBtn?.querySelector('i'); if (!icon) return; if (isPlaying) { icon.classList.remove('fa-play'); icon.classList.add('fa-pause'); } else { icon.classList.remove('fa-pause'); icon.classList.add('fa-play'); } } function playPrevious() { // Implement previous track logic showToast('Non disponible pour le moment', 'error'); } function playNext() { // Implement next track logic showToast('Non disponible pour le moment', 'error'); } function toggleShuffle() { AppState.isShuffle = !AppState.isShuffle; if (DOM.shuffleBtn) { DOM.shuffleBtn.classList.toggle('active', AppState.isShuffle); } showToast(AppState.isShuffle ? 'Aléatoire activé' : 'Aléatoire désactivé', 'success'); } function toggleRepeat() { const modes = ['none', 'all', 'one']; const currentIndex = modes.indexOf(AppState.repeatMode); const nextIndex = (currentIndex + 1) % modes.length; AppState.repeatMode = modes[nextIndex]; if (DOM.repeatBtn) { DOM.repeatBtn.classList.remove('active'); if (AppState.repeatMode !== 'none') { DOM.repeatBtn.classList.add('active'); } } const messages = { none: 'Répétition désactivée', all: 'Répétition de toutes les pistes', one: 'Répétition de la piste actuelle' }; showToast(messages[AppState.repeatMode], 'success'); } function handleSeek() { if (!DOM.audioPlayer || !DOM.progressBar) return; const time = (DOM.progressBar.value / 100) * DOM.audioPlayer.duration; DOM.audioPlayer.currentTime = time; } function handleVolumeChange() { if (!DOM.audioPlayer || !DOM.volumeBar) return; AppState.volume = DOM.volumeBar.value; DOM.audioPlayer.volume = AppState.volume / 100; AppState.isMuted = false; updateVolumeIcon(); } function toggleMute() { if (!DOM.audioPlayer) return; AppState.isMuted = !AppState.isMuted; DOM.audioPlayer.muted = AppState.isMuted; updateVolumeIcon(); } function updateVolumeIcon() { const icon = DOM.muteBtn?.querySelector('i'); if (!icon) return; icon.className = 'fas'; if (AppState.isMuted || AppState.volume === 0) { icon.classList.add('fa-volume-mute'); } else if (AppState.volume < 50) { icon.classList.add('fa-volume-down'); } else { icon.classList.add('fa-volume-up'); } } function toggleLike() { if (!DOM.likeBtn) return; const trackId = DOM.likeBtn.dataset.trackId; if (!trackId) return; if (AppState.likedTracks.has(trackId)) { AppState.likedTracks.delete(trackId); DOM.likeBtn.classList.remove('liked'); DOM.likeBtn.querySelector('i').classList.replace('fas', 'far'); showToast('Retiré des titres likés', 'success'); } else { AppState.likedTracks.add(trackId); DOM.likeBtn.classList.add('liked'); DOM.likeBtn.querySelector('i').classList.replace('far', 'fas'); showToast('Ajouté aux titres likés', 'success'); } } function updateProgress() { if (!DOM.audioPlayer || !DOM.progressBar) return; const progress = (DOM.audioPlayer.currentTime / DOM.audioPlayer.duration) * 100; DOM.progressBar.value = progress; if (DOM.currentTime) { DOM.currentTime.textContent = formatTime(DOM.audioPlayer.currentTime); } } function updateDuration() { if (!DOM.audioPlayer || !DOM.totalTime) return; DOM.totalTime.textContent = formatTime(DOM.audioPlayer.duration); } function handleTrackEnd() { if (AppState.repeatMode === 'one') { DOM.audioPlayer.currentTime = 0; DOM.audioPlayer.play(); } else if (AppState.repeatMode === 'all') { playNext(); } else { updatePlayButton(false); } } // ============================================ // UTILITY FUNCTIONS // ============================================ function formatTime(seconds) { if (!seconds || isNaN(seconds)) return '0:00'; const mins = Math.floor(seconds / 60); const secs = Math.floor(seconds % 60); return `${mins}:${secs.toString().padStart(2, '0')}`; } function showScreen(screen) { if (DOM.loadingScreen) DOM.loadingScreen.classList.add('hidden'); if (DOM.loginScreen) DOM.loginScreen.classList.toggle('hidden', screen !== 'login'); if (DOM.mainApp) { DOM.mainApp.classList.toggle('hidden', screen !== 'main'); if (screen === 'main') { DOM.mainApp.classList.add('visible'); } } } function hideLoadingScreen() { if (DOM.loadingScreen) { setTimeout(() => { DOM.loadingScreen.style.display = 'none'; }, 500); } } function showError(message) { if (DOM.authError) { DOM.authError.textContent = message; DOM.authError.classList.remove('hidden'); } } async function loadUserData() { // Load playlists, liked tracks, etc. await loadPlaylists(); await loadTrendingTracks(); } async function loadPlaylists() { const container = document.getElementById('my-playlists'); if (!container) return; try { const token = localStorage.getItem('token'); const response = await fetch('/api/v1/playlists', { headers: { 'Authorization': `Bearer ${token}` } }); if (response.ok) { const playlists = await response.json(); AppState.playlists = playlists; renderPlaylists(playlists); } } catch (error) { console.error('Failed to load playlists:', error); container.innerHTML = '
Erreur de chargement
'; } } function renderPlaylists(playlists) { const container = document.getElementById('my-playlists'); if (!container) return; if (playlists.length === 0) { container.innerHTML = 'Aucune playlist
'; return; } container.innerHTML = playlists.map(playlist => `
${playlist.track_count || 0} pistes
Erreur de chargement
'; } } function renderTracks(tracks, container) { if (!container) return; if (tracks.length === 0) { container.innerHTML = 'Aucun résultat
'; return; } container.innerHTML = tracks.map(track => `
${track.artist}