import '@fontsource-variable/inter' import { useQueryClient } from '@tanstack/react-query' import { PersistQueryClientProvider } from '@tanstack/react-query-persist-client' import { useCallback, useEffect, useState } from 'react' import { Navigate, Route, BrowserRouter as Router, Routes } from 'react-router-dom' import './App.css' import { Dropdown } from './components/Dropdown' import { ErrorBoundary } from './components/ErrorBoundary' import './components/ErrorBoundary.css' import { Main } from './components/Main' import './components/MediaList.css' import './components/SpacingFix.css' import { PageRefreshPrevention } from './components/PageRefreshPrevention' import { Sidenav } from './components/Sidenav' import { AudioStorageContextProvider } from './context/AudioStorageContext/AudioStorageContextProvider' import { DownloadContextProvider } from './context/DownloadContext/DownloadContextProvider' import { PlaybackContextProvider } from './context/PlaybackContext/PlaybackContextProvider' import { useDropdownContext } from './context/DropdownContext/DropdownContext' import { DropdownContextProvider } from './context/DropdownContext/DropdownContextProvider' import { HistoryContextProvider } from './context/HistoryContext/HistoryContextProvider' import { JellyfinContextProvider } from './context/JellyfinContext/JellyfinContextProvider' import { PageTitleProvider } from './context/PageTitleContext/PageTitleProvider' import { ScrollContextProvider } from './context/ScrollContext/ScrollContextProvider' import { useSidenavContext } from './context/SidenavContext/SidenavContext' import { SidenavContextProvider } from './context/SidenavContext/SidenavContextProvider' import { ThemeContextProvider } from './context/ThemeContext/ThemeContextProvider' import { useDocumentTitle } from './hooks/useDocumentTitle' import { Album } from './pages/Album' import { AlbumArtists } from './pages/AlbumArtists' import { Albums } from './pages/Albums' import { Artist } from './pages/Artist' import { Artists } from './pages/Artists'; import { Discover } from './pages/Discover'; import { DiscoverOptimized } from './pages/DiscoverOptimized'; import { ArtistTracks } from './pages/ArtistTracks' import { Downloads } from './pages/Downloads' import { Favorites } from './pages/Favorites' import { FrequentlyPlayed } from './pages/FrequentlyPlayed' import { Genre } from './pages/Genre' import { Genres } from './pages/Genres' import { GenresOverview } from './pages/GenresOverview' import { Home } from './pages/Home' import { HomeEnhanced } from './pages/HomeEnhanced' import { HomeEnhancedV2 } from './pages/HomeEnhancedV2' import { InstantMix } from './pages/InstantMix' import { Login } from './pages/Login' import { Lyrics } from './pages/Lyrics' import { NowPlaying } from './pages/NowPlaying' import { NowPlayingLyrics } from './pages/NowPlayingLyrics' import { Playlist } from './pages/Playlist' import { Queue } from './pages/Queue' import { RecentlyPlayed } from './pages/RecentlyPlayed' import { SearchResults } from './pages/SearchResults' import { SectionMore } from './pages/SectionMore' import { Settings } from './pages/Settings' import { Tracks } from './pages/Tracks' import { OnlineSearch } from './pages/OnlineSearch' import { persister, queryClient } from './queryClient' import { memoryManager } from './utils/memoryManager' export const App = () => { useEffect(() => { // Debug logging for mobile const isMobile = /Android|webOS|iPhone|iPad|iPod|BlackBerry|IEMobile|Opera Mini/i.test(navigator.userAgent); const isIPad = /iPad/i.test(navigator.userAgent) || (navigator.platform === 'MacIntel' && navigator.maxTouchPoints > 1); try { localStorage.setItem('app-debug', JSON.stringify({ timestamp: Date.now(), userAgent: navigator.userAgent, isMobile, isIPad, location: window.location.href, step: 'App useEffect started' })); } catch (e) { console.error('Failed to write debug info:', e); } // Initialize memory manager const cleanup = memoryManager.onMemoryWarning(() => { console.log('Memory warning in App - cleaning up React Query cache'); queryClient.clear(); }); // Make query client available globally for memory manager (window as any).queryClient = queryClient; // Initialize blank screen prevention (only on desktop, not on mobile or iPad) if (!isMobile && !isIPad) { import('./utils/blankScreenPrevention').then(({ blankScreenPrevention }) => { blankScreenPrevention.checkForRecovery(); blankScreenPrevention.startMonitoring(); }).catch(console.error); } try { localStorage.setItem('app-debug', JSON.stringify({ timestamp: Date.now(), userAgent: navigator.userAgent, isMobile, isIPad, location: window.location.href, step: 'App useEffect completed' })); } catch (e) { console.error('Failed to write debug info:', e); } return () => { cleanup(); if (!isMobile && !isIPad) { import('./utils/blankScreenPrevention').then(({ blankScreenPrevention }) => { blankScreenPrevention.stopMonitoring(); }).catch(console.error); } }; }, []); return ( ) } const RoutedApp = () => { const [auth, setAuth] = useState(() => { const savedAuth = localStorage.getItem('auth') return savedAuth ? JSON.parse(savedAuth) : null }) const [isLoggingOut, setIsLoggingOut] = useState(false) const [forceRenderKey, setForceRenderKey] = useState(0) const queryClient = useQueryClient() const handleLogin = (authData: AuthData) => { setAuth(authData) localStorage.setItem('auth', JSON.stringify(authData)) } const handleLogout = async () => { setIsLoggingOut(true) localStorage.removeItem('repeatMode') setAuth(null) localStorage.removeItem('auth') setIsLoggingOut(false) queryClient.clear() await persister.removeClient() } useEffect(() => { if (!auth) { localStorage.removeItem('auth') } }, [auth]) // Listen for force re-render events from blank screen prevention useEffect(() => { const handleForceRerender = (event: CustomEvent) => { console.log('๐ Force re-render requested:', event.detail); setForceRenderKey(prev => prev + 1); }; window.addEventListener('force-rerender', handleForceRerender as EventListener); return () => { window.removeEventListener('force-rerender', handleForceRerender as EventListener); }; }, []) useEffect(() => { const isWindows = /Win/.test(navigator.userAgent) const isChromium = /Chrome/.test(navigator.userAgent) && /Google Inc/.test(navigator.vendor) const isEdge = /Edg/.test(navigator.userAgent) && /Microsoft Corporation/.test(navigator.vendor) const isIOS = /iPad|iPhone|iPod/.test(navigator.userAgent) && !(window as any).MSStream // eslint-disable-line @typescript-eslint/no-explicit-any const isAndroidPWA = /Android/.test(navigator.userAgent) && (window.matchMedia('(display-mode: standalone)').matches || (navigator as any).standalone) // eslint-disable-line @typescript-eslint/no-explicit-any if (isWindows && (isChromium || isEdge)) { document.getElementsByTagName('html')[0].classList.add('winOS') } if (isIOS) { document.getElementsByTagName('html')[0].classList.add('iOS') } // env safe area inset not supported or unreliable in android pwa? if (isAndroidPWA) { document.getElementsByTagName('html')[0].classList.add('safeAreaFallback') } // Needed for iOS, else you need to refresh page after changing orientation mode const updateOrientation = () => { const isIOSSafariNonPWA = isIOS && !window.matchMedia('(display-mode: standalone)').matches && !(navigator as any).standalone && // eslint-disable-line @typescript-eslint/no-explicit-any !window.matchMedia('(orientation: landscape)').matches const htmlElement = document.getElementsByTagName('html')[0] if (isIOS) { if (isIOSSafariNonPWA) { htmlElement.classList.add('safeAreaFallback') } else { htmlElement.classList.remove('safeAreaFallback') } } } updateOrientation() window.matchMedia('(orientation: landscape)').addEventListener('change', updateOrientation) return () => { window.matchMedia('(orientation: landscape)').removeEventListener('change', updateOrientation) } }, []) // Debug: Add test functions to window for manual testing useEffect(() => { if (process.env.NODE_ENV === 'development') { import('./utils/networkStabilityTest').then(({ networkStabilityTest }) => { (window as any).testNetworkStability = () => networkStabilityTest.runAllTests(); console.log('๐ง Debug: Run window.testNetworkStability() to test network stability features'); }); // Add blank screen test (window as any).testBlankScreen = () => { console.log('๐งช Simulating blank screen condition...'); throw new Error('Simulated blank screen error for testing'); }; // Add force re-render test (window as any).forceRerender = () => { console.log('๐ Forcing app re-render...'); window.dispatchEvent(new CustomEvent('force-rerender', { detail: { timestamp: Date.now(), reason: 'manual-test' } })); }; // Debug functions removed - using simple Media Session API approach console.log('๐ง Debug commands available:'); console.log(' - window.testNetworkStability() - Test network features'); console.log(' - window.testBlankScreen() - Simulate blank screen'); console.log(' - window.forceRerender() - Force app re-render'); console.log(' - window.checkPlayback() - Check current playback state'); console.log(' - window.testBackground() - Test background playback'); console.log(' - window.getPlaybackLogs() - Get recent playback logs'); console.log(' - window.getPlaybackReport() - Get full debug report'); } }, []); const actualApp = ( : } /> ) : ( ) } /> ) return ( {actualApp} ) } interface AuthData { serverUrl: string token: string userId: string username: string } const MainLayout = ({ auth, handleLogout }: { auth: AuthData; handleLogout: () => void }) => { useDocumentTitle(); const memoSettings = useCallback(() => , [handleLogout]); const { showSidenav, toggleSidenav } = useSidenavContext() const dropdownContext = useDropdownContext() const { isOpen: isDropdownOpen } = dropdownContext const isTouchDevice = 'ontouchstart' in window return ( } /> } /> {/* Safe area spacer for iOS status bar */} {/* Swipe area for sidebar */} { const touch = e.touches[0]; const screenWidth = window.innerWidth; if (touch.clientX >= screenWidth - 20) { // Only trigger if touch starts within 20px of right edge const startX = touch.clientX; const startY = touch.clientY; const handleTouchMove = (moveEvent: TouchEvent) => { const moveTouch = moveEvent.touches[0]; const deltaX = startX - moveTouch.clientX; // Reversed for right-to-left swipe const deltaY = moveTouch.clientY - startY; // Only trigger if horizontal swipe is dominant and goes left if (deltaX > 50 && Math.abs(deltaY) < Math.abs(deltaX)) { if (!showSidenav) { toggleSidenav(); } document.removeEventListener('touchmove', handleTouchMove); document.removeEventListener('touchend', handleTouchEnd); } }; const handleTouchEnd = () => { document.removeEventListener('touchmove', handleTouchMove); document.removeEventListener('touchend', handleTouchEnd); }; document.addEventListener('touchmove', handleTouchMove); document.addEventListener('touchend', handleTouchEnd); } }} /> Page Error This page encountered an error. Try navigating to a different page. window.location.href = '/'} className="retry-button"> Go Home }> } /> } /> } /> } /> } /> } /> } /> } /> } /> } /> } /> } /> } /> } /> } /> } /> } /> } /> } /> } /> } /> } /> } /> } /> } /> } /> } /> ) }
This page encountered an error. Try navigating to a different page.