feat: implement volume persistence and auto-play control in AudioPlayer and AudioPlayerContext

This commit is contained in:
2025-06-20 02:47:12 +00:00
committed by GitHub
parent 96a29f25dc
commit 6d6b1baa62
3 changed files with 39 additions and 14 deletions

View File

@@ -31,6 +31,19 @@ export const AudioPlayer: React.FC = () => {
useEffect(() => { useEffect(() => {
setIsClient(true); setIsClient(true);
// Load saved volume
const savedVolume = localStorage.getItem('navidrome-volume');
if (savedVolume) {
try {
const volumeValue = parseFloat(savedVolume);
if (volumeValue >= 0 && volumeValue <= 1) {
setVolume(volumeValue);
}
} catch (error) {
console.error('Failed to parse saved volume:', error);
}
}
// Clean up old localStorage entries with track IDs // Clean up old localStorage entries with track IDs
const keysToRemove: string[] = []; const keysToRemove: string[] = [];
for (let i = 0; i < localStorage.length; i++) { for (let i = 0; i < localStorage.length; i++) {
@@ -42,6 +55,16 @@ export const AudioPlayer: React.FC = () => {
keysToRemove.forEach(key => localStorage.removeItem(key)); keysToRemove.forEach(key => localStorage.removeItem(key));
}, []); }, []);
// Apply volume to audio element when volume changes
useEffect(() => {
const audioCurrent = audioRef.current;
if (audioCurrent) {
audioCurrent.volume = volume;
}
// Save volume to localStorage
localStorage.setItem('navidrome-volume', volume.toString());
}, [volume]);
// Save position when component unmounts or track changes // Save position when component unmounts or track changes
useEffect(() => { useEffect(() => {
const audioCurrent = audioRef.current; const audioCurrent = audioRef.current;
@@ -248,9 +271,6 @@ export const AudioPlayer: React.FC = () => {
const handleVolumeChange = (e: React.ChangeEvent<HTMLInputElement>) => { const handleVolumeChange = (e: React.ChangeEvent<HTMLInputElement>) => {
const newVolume = parseFloat(e.target.value); const newVolume = parseFloat(e.target.value);
setVolume(newVolume); setVolume(newVolume);
if (audioCurrent) {
audioCurrent.volume = newVolume;
}
}; };
function formatTime(seconds: number): string { function formatTime(seconds: number): string {

View File

@@ -69,7 +69,10 @@ export const AudioPlayerProvider: React.FC<{ children: React.ReactNode }> = ({ c
const savedCurrentTrack = localStorage.getItem('navidrome-currentTrack'); const savedCurrentTrack = localStorage.getItem('navidrome-currentTrack');
if (savedCurrentTrack) { if (savedCurrentTrack) {
try { try {
setCurrentTrack(JSON.parse(savedCurrentTrack)); const track = JSON.parse(savedCurrentTrack);
// Clear autoPlay flag when loading from localStorage to prevent auto-play on refresh
track.autoPlay = false;
setCurrentTrack(track);
} catch (error) { } catch (error) {
console.error('Failed to parse saved current track:', error); console.error('Failed to parse saved current track:', error);
} }
@@ -78,7 +81,9 @@ export const AudioPlayerProvider: React.FC<{ children: React.ReactNode }> = ({ c
useEffect(() => { useEffect(() => {
if (currentTrack) { if (currentTrack) {
localStorage.setItem('navidrome-currentTrack', JSON.stringify(currentTrack)); // Remove autoPlay flag when saving to localStorage
const { autoPlay, ...trackToSave } = currentTrack;
localStorage.setItem('navidrome-currentTrack', JSON.stringify(trackToSave));
} else { } else {
localStorage.removeItem('navidrome-currentTrack'); localStorage.removeItem('navidrome-currentTrack');
} }
@@ -282,11 +287,11 @@ export const AudioPlayerProvider: React.FC<{ children: React.ReactNode }> = ({ c
} }
// Play the first shuffled track and set the rest as queue // Play the first shuffled track and set the rest as queue
playTrack(shuffledTracks[0]); playTrack(shuffledTracks[0], true); // Enable autoplay
setQueue(shuffledTracks.slice(1)); setQueue(shuffledTracks.slice(1));
} else { } else {
// Normal order: play first track and set the rest as queue // Normal order: play first track and set the rest as queue
playTrack(tracks[0]); playTrack(tracks[0], true); // Enable autoplay
setQueue(tracks.slice(1)); setQueue(tracks.slice(1));
} }
} }
@@ -332,11 +337,11 @@ export const AudioPlayerProvider: React.FC<{ children: React.ReactNode }> = ({ c
} }
setQueue(remainingTracks); setQueue(remainingTracks);
playTrack(tracks[startingIndex]); playTrack(tracks[startingIndex], true); // Enable autoplay
} else { } else {
// Normal order: set the remaining tracks after the starting track as queue // Normal order: set the remaining tracks after the starting track as queue
setQueue(tracks.slice(startingIndex + 1)); setQueue(tracks.slice(startingIndex + 1));
playTrack(tracks[startingIndex]); playTrack(tracks[startingIndex], true); // Enable autoplay
} }
toast({ toast({
@@ -360,8 +365,8 @@ export const AudioPlayerProvider: React.FC<{ children: React.ReactNode }> = ({ c
const targetTrack = queue[index]; const targetTrack = queue[index];
// Remove all tracks before the target track (including the target track) // Remove all tracks before the target track (including the target track)
setQueue((prevQueue) => prevQueue.slice(index + 1)); setQueue((prevQueue) => prevQueue.slice(index + 1));
// Play the target track // Play the target track with autoplay enabled
playTrack(targetTrack); playTrack(targetTrack, true);
} }
}, [queue, playTrack]); }, [queue, playTrack]);
@@ -445,11 +450,11 @@ export const AudioPlayerProvider: React.FC<{ children: React.ReactNode }> = ({ c
} }
// Play the first shuffled track and set the rest as queue // Play the first shuffled track and set the rest as queue
playTrack(shuffledTracks[0]); playTrack(shuffledTracks[0], true); // Enable autoplay
setQueue(shuffledTracks.slice(1)); setQueue(shuffledTracks.slice(1));
} else { } else {
// Normal order: play first track and set the rest as queue // Normal order: play first track and set the rest as queue
playTrack(allTracks[0]); playTrack(allTracks[0], true); // Enable autoplay
setQueue(allTracks.slice(1)); setQueue(allTracks.slice(1));
} }
} }

View File

@@ -334,7 +334,7 @@ export const FullScreenPlayer: React.FC<FullScreenPlayerProps> = ({ isOpen, onCl
<h1 className="text-lg sm:text-xl lg:text-3xl font-bold text-foreground mb-2 line-clamp-2 leading-tight"> <h1 className="text-lg sm:text-xl lg:text-3xl font-bold text-foreground mb-2 line-clamp-2 leading-tight">
{currentTrack.name} {currentTrack.name}
</h1> </h1>
<Link href={`/album${currentTrack.artistId}`} className="text-base sm:text-lg lg:text-xl text-foreground/80 mb-1 line-clamp-1"> <Link href={`/album/${currentTrack.artistId}`} className="text-base sm:text-lg lg:text-xl text-foreground/80 mb-1 line-clamp-1">
{currentTrack.artist} {currentTrack.artist}
</Link> </Link>
</div> </div>