import { LosslessAPI } from './api.js'; import { apiSettings, themeManager, nowPlayingSettings } from './storage.js'; import { UIRenderer } from './ui.js'; import { Player } from './player.js'; import { LastFMScrobbler } from './lastfm.js'; import { LyricsManager, createLyricsPanel, showKaraokeView } from './lyrics.js'; import { createRouter, updateTabTitle } from './router.js'; import { initializeSettings } from './settings.js'; import { initializePlayerEvents, initializeTrackInteractions } from './events.js'; import { initializeUIInteractions } from './ui-interactions.js'; import { downloadAlbumAsZip, downloadDiscography, downloadCurrentTrack } from './downloads.js'; import { debounce, SVG_PLAY } from './utils.js'; function initializeCasting(audioPlayer, castBtn) { if (!castBtn) return; // Check for Remote Playback API (Chrome) if ('remote' in audioPlayer) { audioPlayer.remote.watchAvailability((available) => { if (available) { castBtn.style.display = 'flex'; castBtn.classList.add('available'); } }).catch(err => { console.log('Remote playback not available:', err); // Still show button on desktop if (window.innerWidth > 768) { castBtn.style.display = 'flex'; } }); castBtn.addEventListener('click', () => { audioPlayer.remote.prompt().catch(err => { console.log('Cast prompt error:', err); }); }); // Listen for connection state changes audioPlayer.addEventListener('playing', () => { if (audioPlayer.remote && audioPlayer.remote.state === 'connected') { castBtn.classList.add('connected'); } }); audioPlayer.addEventListener('pause', () => { if (audioPlayer.remote && audioPlayer.remote.state === 'disconnected') { castBtn.classList.remove('connected'); } }); } // Check for AirPlay (Safari) else if (audioPlayer.webkitShowPlaybackTargetPicker) { castBtn.style.display = 'flex'; castBtn.classList.add('available'); castBtn.addEventListener('click', () => { audioPlayer.webkitShowPlaybackTargetPicker(); }); // Listen for AirPlay connection state audioPlayer.addEventListener('webkitplaybacktargetavailabilitychanged', (e) => { if (e.availability === 'available') { castBtn.classList.add('available'); } }); audioPlayer.addEventListener('webkitcurrentplaybacktargetiswirelesschanged', () => { if (audioPlayer.webkitCurrentPlaybackTargetIsWireless) { castBtn.classList.add('connected'); } else { castBtn.classList.remove('connected'); } }); } // Show on desktop anyway else if (window.innerWidth > 768) { castBtn.style.display = 'flex'; castBtn.addEventListener('click', () => { alert('Casting is not supported in this browser. Try Chrome for Chromecast or Safari for AirPlay.'); }); } } function initializeKeyboardShortcuts(player, audioPlayer) { document.addEventListener('keydown', (e) => { // Don't trigger shortcuts when typing in inputs if (e.target.matches('input, textarea')) return; switch(e.key.toLowerCase()) { case ' ': e.preventDefault(); player.handlePlayPause(); break; case 'arrowright': if (e.shiftKey) { player.playNext(); } else { audioPlayer.currentTime = Math.min( audioPlayer.duration, audioPlayer.currentTime + 10 ); } break; case 'arrowleft': if (e.shiftKey) { player.playPrev(); } else { audioPlayer.currentTime = Math.max(0, audioPlayer.currentTime - 10); } break; case 'arrowup': e.preventDefault(); audioPlayer.volume = Math.min(1, audioPlayer.volume + 0.1); break; case 'arrowdown': e.preventDefault(); audioPlayer.volume = Math.max(0, audioPlayer.volume - 0.1); break; case 'm': audioPlayer.muted = !audioPlayer.muted; break; case 's': document.getElementById('shuffle-btn')?.click(); break; case 'r': document.getElementById('repeat-btn')?.click(); break; case 'q': document.getElementById('queue-btn')?.click(); break; case '/': e.preventDefault(); document.getElementById('search-input')?.focus(); break; case 'escape': document.getElementById('search-input')?.blur(); document.getElementById('queue-modal-overlay').style.display = 'none'; const lyricsPanel = document.getElementById('lyrics-panel'); if (lyricsPanel) { lyricsPanel.classList.add('hidden'); } const karaokeView = document.getElementById('karaoke-view'); if (karaokeView) { karaokeView.remove(); } break; case 'l': // Toggle lyrics document.querySelector('.now-playing-bar .cover')?.click(); break; } }); } function initializeMediaSessionHandlers(player) { if (!('mediaSession' in navigator)) return; try { navigator.mediaSession.setActionHandler('seekto', (details) => { if (details.seekTime !== undefined && details.fastSeek !== undefined && details.fastSeek) { player.audio.currentTime = details.seekTime; player.updateMediaSessionPositionState(); } }); } catch (error) { console.log('seekto action not supported'); } } function showOfflineNotification() { const notification = document.createElement('div'); notification.className = 'offline-notification'; notification.innerHTML = ` You are offline. Some features may not work. `; document.body.appendChild(notification); setTimeout(() => { notification.style.animation = 'slideOut 0.3s ease forwards'; setTimeout(() => notification.remove(), 300); }, 5000); } function hideOfflineNotification() { const notification = document.querySelector('.offline-notification'); if (notification) { notification.style.animation = 'slideOut 0.3s ease forwards'; setTimeout(() => notification.remove(), 300); } } document.addEventListener('DOMContentLoaded', async () => { const api = new LosslessAPI(apiSettings); const ui = new UIRenderer(api); const audioPlayer = document.getElementById('audio-player'); const currentQuality = localStorage.getItem('playback-quality') || 'LOSSLESS'; const player = new Player(audioPlayer, api, currentQuality); const scrobbler = new LastFMScrobbler(); const lyricsManager = new LyricsManager(api); const lyricsPanel = createLyricsPanel(); const currentTheme = themeManager.getTheme(); themeManager.setTheme(currentTheme); // Initialize all modules initializeSettings(scrobbler, player, api, ui); initializePlayerEvents(player, audioPlayer, scrobbler); initializeTrackInteractions(player, api, document.querySelector('.main-content'), document.getElementById('context-menu')); initializeUIInteractions(player, api); initializeKeyboardShortcuts(player, audioPlayer); initializeMediaSessionHandlers(player); // Initialize casting const castBtn = document.getElementById('cast-btn'); initializeCasting(audioPlayer, castBtn); // Album art click handler for lyrics document.querySelector('.now-playing-bar .cover').addEventListener('click', async () => { if (!player.currentTrack) { alert('No track is currently playing'); return; } const mode = nowPlayingSettings.getMode(); if (mode === 'karaoke') { // Show karaoke view lyricsPanel.classList.add('hidden'); const lyricsData = await lyricsManager.fetchLyrics(player.currentTrack.id); if (lyricsData) { showKaraokeView(player.currentTrack, lyricsData, audioPlayer); } else { alert('No lyrics available for this track'); } } else if (mode === 'lyrics') { // Toggle lyrics panel const isHidden = lyricsPanel.classList.contains('hidden'); lyricsPanel.classList.toggle('hidden'); if (isHidden) { const content = lyricsPanel.querySelector('.lyrics-content'); content.innerHTML = '
${line || ' '}
` ).join(''); } else { content.innerHTML = 'A new version of Monochrome is available.
Install this app for a better experience.