feat: implement auto-play functionality in AudioPlayer and update playTrack method in AudioPlayerContext

This commit is contained in:
2025-06-19 22:26:13 +00:00
committed by GitHub
parent 3cc1e4d441
commit c246c2466a
4 changed files with 50 additions and 38 deletions

View File

View File

@@ -84,8 +84,17 @@ export const AudioPlayer: React.FC = () => {
localStorage.removeItem('navidrome-current-track-time'); localStorage.removeItem('navidrome-current-track-time');
} }
audioCurrent.play(); // Auto-play only if the track has the autoPlay flag
setIsPlaying(true); if (currentTrack.autoPlay) {
audioCurrent.play().then(() => {
setIsPlaying(true);
}).catch((error) => {
console.error('Failed to auto-play:', error);
setIsPlaying(false);
});
} else {
setIsPlaying(false);
}
} }
}, [currentTrack]); }, [currentTrack]);
@@ -121,11 +130,21 @@ export const AudioPlayer: React.FC = () => {
lastSavedTime = audioCurrent.currentTime; lastSavedTime = audioCurrent.currentTime;
} }
}; };
const handlePlay = () => {
setIsPlaying(true);
};
const handlePause = () => {
setIsPlaying(false);
};
if (audioCurrent) { if (audioCurrent) {
audioCurrent.addEventListener('timeupdate', updateProgress); audioCurrent.addEventListener('timeupdate', updateProgress);
audioCurrent.addEventListener('ended', handleTrackEnd); audioCurrent.addEventListener('ended', handleTrackEnd);
audioCurrent.addEventListener('seeked', handleSeeked); audioCurrent.addEventListener('seeked', handleSeeked);
audioCurrent.addEventListener('play', handlePlay);
audioCurrent.addEventListener('pause', handlePause);
} }
return () => { return () => {
@@ -133,6 +152,8 @@ export const AudioPlayer: React.FC = () => {
audioCurrent.removeEventListener('timeupdate', updateProgress); audioCurrent.removeEventListener('timeupdate', updateProgress);
audioCurrent.removeEventListener('ended', handleTrackEnd); audioCurrent.removeEventListener('ended', handleTrackEnd);
audioCurrent.removeEventListener('seeked', handleSeeked); audioCurrent.removeEventListener('seeked', handleSeeked);
audioCurrent.removeEventListener('play', handlePlay);
audioCurrent.removeEventListener('pause', handlePause);
} }
}; };
}, [playNextTrack, currentTrack]); }, [playNextTrack, currentTrack]);
@@ -213,10 +234,15 @@ export const AudioPlayer: React.FC = () => {
if (audioCurrent) { if (audioCurrent) {
if (isPlaying) { if (isPlaying) {
audioCurrent.pause(); audioCurrent.pause();
setIsPlaying(false);
} else { } else {
audioCurrent.play(); audioCurrent.play().then(() => {
setIsPlaying(true);
}).catch((error) => {
console.error('Failed to play audio:', error);
setIsPlaying(false);
});
} }
setIsPlaying(!isPlaying);
} }
}; };
const handleVolumeChange = (e: React.ChangeEvent<HTMLInputElement>) => { const handleVolumeChange = (e: React.ChangeEvent<HTMLInputElement>) => {

View File

@@ -15,11 +15,12 @@ interface Track {
coverArt?: string; coverArt?: string;
albumId: string; albumId: string;
artistId: string; artistId: string;
autoPlay?: boolean; // Flag to control auto-play
} }
interface AudioPlayerContextProps { interface AudioPlayerContextProps {
currentTrack: Track | null; currentTrack: Track | null;
playTrack: (track: Track) => void; playTrack: (track: Track, autoPlay?: boolean) => void;
queue: Track[]; queue: Track[];
addToQueue: (track: Track) => void; addToQueue: (track: Track) => void;
playNextTrack: () => void; playNextTrack: () => void;
@@ -92,14 +93,17 @@ export const AudioPlayerProvider: React.FC<{ children: React.ReactNode }> = ({ c
}; };
}, [api]); }, [api]);
const playTrack = useCallback((track: Track) => { const playTrack = useCallback((track: Track, autoPlay: boolean = false) => {
// Clear saved timestamp when manually playing a track // Clear saved timestamp when manually playing a track
localStorage.removeItem('navidrome-current-track-time'); localStorage.removeItem('navidrome-current-track-time');
if (currentTrack) { if (currentTrack) {
setPlayedTracks((prev) => [...prev, currentTrack]); setPlayedTracks((prev) => [...prev, currentTrack]);
} }
setCurrentTrack(track);
// Set autoPlay flag on the track
const trackWithAutoPlay = { ...track, autoPlay };
setCurrentTrack(trackWithAutoPlay);
// Scrobble the track // Scrobble the track
api.scrobble(track.id).catch(error => { api.scrobble(track.id).catch(error => {
@@ -126,7 +130,7 @@ export const AudioPlayerProvider: React.FC<{ children: React.ReactNode }> = ({ c
if (queue.length > 0) { if (queue.length > 0) {
const nextTrack = queue[0]; const nextTrack = queue[0];
setQueue((prevQueue) => prevQueue.slice(1)); setQueue((prevQueue) => prevQueue.slice(1));
playTrack(nextTrack); playTrack(nextTrack, true); // Auto-play next track
} }
}, [queue, playTrack]); }, [queue, playTrack]);
@@ -143,9 +147,9 @@ export const AudioPlayerProvider: React.FC<{ children: React.ReactNode }> = ({ c
setQueue((prevQueue) => [currentTrack, ...prevQueue]); setQueue((prevQueue) => [currentTrack, ...prevQueue]);
} }
setCurrentTrack(previousTrack); playTrack(previousTrack, true); // Auto-play previous track
} }
}, [playedTracks, currentTrack]); }, [playedTracks, currentTrack, playTrack]);
const addAlbumToQueue = useCallback(async (albumId: string) => { const addAlbumToQueue = useCallback(async (albumId: string) => {
setIsLoading(true); setIsLoading(true);

View File

@@ -139,53 +139,35 @@ export const FullScreenPlayer: React.FC<FullScreenPlayerProps> = ({ isOpen, onCl
// Sync with main audio player (improved responsiveness) // Sync with main audio player (improved responsiveness)
useEffect(() => { useEffect(() => {
let lastUpdate = 0;
const throttleMs = 100; // Update at most every 100ms for better responsiveness
const syncWithMainPlayer = () => { const syncWithMainPlayer = () => {
const now = Date.now();
if (now - lastUpdate < throttleMs) return;
lastUpdate = now;
const mainAudio = document.querySelector('audio') as HTMLAudioElement; const mainAudio = document.querySelector('audio') as HTMLAudioElement;
if (mainAudio && currentTrack) { if (mainAudio && currentTrack) {
const newCurrentTime = mainAudio.currentTime; const newCurrentTime = mainAudio.currentTime;
const newDuration = mainAudio.duration || 0; const newDuration = mainAudio.duration || 0;
const newIsPlaying = !mainAudio.paused; const newIsPlaying = !mainAudio.paused;
// Always update playing state for better responsiveness // Always update playing state immediately
if (newIsPlaying !== isPlaying) { setIsPlaying(newIsPlaying);
setIsPlaying(newIsPlaying); setCurrentTime(newCurrentTime);
} setDuration(newDuration);
setVolume(mainAudio.volume);
// Only update state if values have changed significantly
if (Math.abs(newCurrentTime - currentTime) > 0.3) {
setCurrentTime(newCurrentTime);
}
if (Math.abs(newDuration - duration) > 0.1) {
setDuration(newDuration);
}
if (newDuration > 0) { if (newDuration > 0) {
const newProgress = (newCurrentTime / newDuration) * 100; const newProgress = (newCurrentTime / newDuration) * 100;
if (Math.abs(newProgress - progress) > 0.1) { setProgress(newProgress);
setProgress(newProgress);
}
}
if (Math.abs(mainAudio.volume - volume) > 0.01) {
setVolume(mainAudio.volume);
} }
} }
}; };
if (isOpen) { if (isOpen && currentTrack) {
// Initial sync // Initial sync
syncWithMainPlayer(); syncWithMainPlayer();
// Set up interval to keep syncing - more frequent for better responsiveness // Set up interval to keep syncing
const interval = setInterval(syncWithMainPlayer, 50); const interval = setInterval(syncWithMainPlayer, 100);
return () => clearInterval(interval); return () => clearInterval(interval);
} }
}, [isOpen, currentTrack]); // Removed other dependencies to prevent loop }, [isOpen, currentTrack?.id]); // React to track changes
// Extract dominant color from cover art // Extract dominant color from cover art
useEffect(() => { useEffect(() => {