feat: implement auto-play functionality in AudioPlayer and update playTrack method in AudioPlayerContext
This commit is contained in:
0
app/api/test-navidrome/route.ts
Normal file
0
app/api/test-navidrome/route.ts
Normal 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>) => {
|
||||||
|
|||||||
@@ -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);
|
||||||
|
|||||||
@@ -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(() => {
|
||||||
|
|||||||
Reference in New Issue
Block a user