From 463be90779d15a06d775d781be4bc69b921fa608 Mon Sep 17 00:00:00 2001 From: angel Date: Wed, 23 Jul 2025 04:05:55 +0000 Subject: [PATCH] feat: enhance mobile audio player with initialization and styling improvements --- .env.local | 2 +- app/components/AudioPlayer.tsx | 127 ++++++++++++++++++++++++++------- app/globals.css | 28 ++++++++ 3 files changed, 130 insertions(+), 27 deletions(-) diff --git a/.env.local b/.env.local index 74514e9..a7d93ce 100644 --- a/.env.local +++ b/.env.local @@ -1 +1 @@ -NEXT_PUBLIC_COMMIT_SHA=437640c +NEXT_PUBLIC_COMMIT_SHA=a6e6588 diff --git a/app/components/AudioPlayer.tsx b/app/components/AudioPlayer.tsx index 462335e..be9b0c6 100644 --- a/app/components/AudioPlayer.tsx +++ b/app/components/AudioPlayer.tsx @@ -94,6 +94,42 @@ export const AudioPlayer: React.FC = () => { } } + // Mobile-specific audio initialization + if (isMobile) { + // Add a document click listener to initialize audio context on first user interaction + const initializeAudioOnMobile = async () => { + if (!audioInitialized) { + try { + const AudioContextClass = window.AudioContext || (window as typeof window & { webkitAudioContext?: typeof AudioContext }).webkitAudioContext; + if (AudioContextClass) { + const audioContext = new AudioContextClass(); + if (audioContext.state === 'suspended') { + await audioContext.resume(); + } + setAudioInitialized(true); + } + } catch (error) { + console.log('Mobile audio context initialization failed:', error); + } + } + }; + + // Listen for any user interaction to initialize audio + const handleFirstUserInteraction = () => { + initializeAudioOnMobile(); + document.removeEventListener('touchstart', handleFirstUserInteraction); + document.removeEventListener('click', handleFirstUserInteraction); + }; + + document.addEventListener('touchstart', handleFirstUserInteraction, { passive: true }); + document.addEventListener('click', handleFirstUserInteraction); + + return () => { + document.removeEventListener('touchstart', handleFirstUserInteraction); + document.removeEventListener('click', handleFirstUserInteraction); + }; + } + // Clean up old localStorage entries with track IDs const keysToRemove: string[] = []; for (let i = 0; i < localStorage.length; i++) { @@ -103,7 +139,7 @@ export const AudioPlayer: React.FC = () => { } } keysToRemove.forEach(key => localStorage.removeItem(key)); - }, []); + }, [isMobile, audioInitialized]); // Apply volume to audio element when volume changes useEffect(() => { @@ -364,47 +400,73 @@ export const AudioPlayer: React.FC = () => { const togglePlayPause = async () => { if (audioCurrent && currentTrack) { - // On mobile, ensure audio is initialized on first user interaction - if (isMobile && !audioInitialized) { - try { - // Create a dummy audio context to initialize audio on mobile - const AudioContextClass = window.AudioContext || (window as typeof window & { webkitAudioContext?: typeof AudioContext }).webkitAudioContext; - if (AudioContextClass) { - const audioContext = new AudioContextClass(); - await audioContext.resume(); - setAudioInitialized(true); - } - } catch (error) { - console.log('Audio context initialization failed:', error); - } - } - if (isPlaying) { audioCurrent.pause(); setIsPlaying(false); onTrackPause(audioCurrent.currentTime); } else { try { + // On mobile, ensure audio element is properly loaded before playing + if (isMobile) { + // Ensure the audio element has the correct source + if (audioCurrent.src !== currentTrack.url) { + audioCurrent.src = currentTrack.url; + audioCurrent.load(); // Force reload the audio element + } + + // Wait for the audio to be ready to play + if (audioCurrent.readyState < 3) { // HAVE_FUTURE_DATA + await new Promise((resolve, reject) => { + const handleCanPlay = () => { + audioCurrent.removeEventListener('canplay', handleCanPlay); + audioCurrent.removeEventListener('error', handleError); + resolve(void 0); + }; + const handleError = () => { + audioCurrent.removeEventListener('canplay', handleCanPlay); + audioCurrent.removeEventListener('error', handleError); + reject(new Error('Audio failed to load')); + }; + audioCurrent.addEventListener('canplay', handleCanPlay); + audioCurrent.addEventListener('error', handleError); + }); + } + } + await audioCurrent.play(); setIsPlaying(true); + setAudioInitialized(true); onTrackPlay(currentTrack); } catch (error) { console.error('Failed to play audio:', error); - // Try to initialize audio context and retry + + // Additional mobile-specific handling if (isMobile) { try { + // Try creating and resuming audio context const AudioContextClass = window.AudioContext || (window as typeof window & { webkitAudioContext?: typeof AudioContext }).webkitAudioContext; if (AudioContextClass) { const audioContext = new AudioContextClass(); - await audioContext.resume(); + if (audioContext.state === 'suspended') { + await audioContext.resume(); + } setAudioInitialized(true); - await audioCurrent.play(); - setIsPlaying(true); - onTrackPlay(currentTrack); } + + // Retry playing + await audioCurrent.play(); + setIsPlaying(true); + onTrackPlay(currentTrack); } catch (retryError) { console.error('Audio play retry failed:', retryError); setIsPlaying(false); + + // Show user-friendly error on mobile + toast({ + variant: "destructive", + title: "Playback Error", + description: "Unable to play audio. Please try again or check your connection.", + }); } } else { setIsPlaying(false); @@ -468,11 +530,13 @@ export const AudioPlayer: React.FC = () => { {/* Mobile controls */}
@@ -688,9 +762,10 @@ export const AudioPlayer: React.FC = () => { ref={audioRef} hidden playsInline - preload="auto" + preload={isMobile ? "none" : "auto"} controls={false} crossOrigin="anonymous" + webkit-playsinline="true" />