feat: implement volume persistence and auto-play control in AudioPlayer and AudioPlayerContext
This commit is contained in:
@@ -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 {
|
||||||
|
|||||||
@@ -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));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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>
|
||||||
|
|||||||
Reference in New Issue
Block a user