feat: add auto-play flag to Track interface in AudioPlayerContext

This commit is contained in:
2025-06-20 00:32:31 +00:00
committed by GitHub
parent 6653420e31
commit 8dfb4b34e5
3 changed files with 150 additions and 41 deletions

View File

@@ -116,8 +116,19 @@ export const AudioPlayerProvider: React.FC<{ children: React.ReactNode }> = ({ c
}, [currentTrack, api]);
const addToQueue = useCallback((track: Track) => {
setQueue((prevQueue) => [...prevQueue, track]);
}, []);
setQueue((prevQueue) => {
if (shuffle && prevQueue.length > 0) {
// If shuffle is enabled, insert the track at a random position
const randomIndex = Math.floor(Math.random() * (prevQueue.length + 1));
const newQueue = [...prevQueue];
newQueue.splice(randomIndex, 0, track);
return newQueue;
} else {
// Normal behavior: add to the end
return [...prevQueue, track];
}
});
}, [shuffle]);
const clearQueue = useCallback(() => {
setQueue([]);
@@ -132,20 +143,13 @@ export const AudioPlayerProvider: React.FC<{ children: React.ReactNode }> = ({ c
localStorage.removeItem('navidrome-current-track-time');
if (queue.length > 0) {
let nextTrack;
if (shuffle) {
// Pick a random track from the queue
const randomIndex = Math.floor(Math.random() * queue.length);
nextTrack = queue[randomIndex];
setQueue((prevQueue) => prevQueue.filter((_, i) => i !== randomIndex));
} else {
// Pick the first track in order
nextTrack = queue[0];
setQueue((prevQueue) => prevQueue.slice(1));
}
// Always pick the first track from the queue
// If shuffle is enabled, the queue will already be shuffled
const nextTrack = queue[0];
setQueue((prevQueue) => prevQueue.slice(1));
playTrack(nextTrack, true); // Auto-play next track
}
}, [queue, playTrack, shuffle]);
}, [queue, playTrack]);
const playPreviousTrack = useCallback(() => {
// Clear saved timestamp when changing tracks
@@ -169,7 +173,29 @@ export const AudioPlayerProvider: React.FC<{ children: React.ReactNode }> = ({ c
try {
const { album, songs } = await api.getAlbum(albumId);
const tracks = songs.map(songToTrack);
setQueue((prevQueue) => [...prevQueue, ...tracks]);
setQueue((prevQueue) => {
if (shuffle && prevQueue.length > 0) {
// If shuffle is enabled, shuffle the new tracks and insert them randomly
const shuffledTracks = [...tracks];
// Fisher-Yates shuffle algorithm
for (let i = shuffledTracks.length - 1; i > 0; i--) {
const j = Math.floor(Math.random() * (i + 1));
[shuffledTracks[i], shuffledTracks[j]] = [shuffledTracks[j], shuffledTracks[i]];
}
// Insert each track at a random position
const newQueue = [...prevQueue];
shuffledTracks.forEach(track => {
const randomIndex = Math.floor(Math.random() * (newQueue.length + 1));
newQueue.splice(randomIndex, 0, track);
});
return newQueue;
} else {
// Normal behavior: add to the end
return [...prevQueue, ...tracks];
}
});
toast({
title: "Album Added",
@@ -185,20 +211,44 @@ export const AudioPlayerProvider: React.FC<{ children: React.ReactNode }> = ({ c
} finally {
setIsLoading(false);
}
}, [api, songToTrack, toast]);
}, [api, songToTrack, toast, shuffle]);
const addArtistToQueue = useCallback(async (artistId: string) => {
setIsLoading(true);
try {
const { artist, albums } = await api.getArtist(artistId);
let allTracks: Track[] = [];
// Add all albums from this artist to queue
// Collect all tracks from all albums
for (const album of albums) {
const { songs } = await api.getAlbum(album.id);
const tracks = songs.map(songToTrack);
setQueue((prevQueue) => [...prevQueue, ...tracks]);
allTracks = allTracks.concat(tracks);
}
setQueue((prevQueue) => {
if (shuffle && prevQueue.length > 0) {
// If shuffle is enabled, shuffle the new tracks and insert them randomly
const shuffledTracks = [...allTracks];
// Fisher-Yates shuffle algorithm
for (let i = shuffledTracks.length - 1; i > 0; i--) {
const j = Math.floor(Math.random() * (i + 1));
[shuffledTracks[i], shuffledTracks[j]] = [shuffledTracks[j], shuffledTracks[i]];
}
// Insert each track at a random position
const newQueue = [...prevQueue];
shuffledTracks.forEach(track => {
const randomIndex = Math.floor(Math.random() * (newQueue.length + 1));
newQueue.splice(randomIndex, 0, track);
});
return newQueue;
} else {
// Normal behavior: add to the end
return [...prevQueue, ...allTracks];
}
});
toast({
title: "Artist Added",
description: `Added all albums by "${artist.name}" to queue`,
@@ -213,24 +263,36 @@ export const AudioPlayerProvider: React.FC<{ children: React.ReactNode }> = ({ c
} finally {
setIsLoading(false);
}
}, [api, songToTrack, toast]);
}, [api, songToTrack, toast, shuffle]);
const playAlbum = useCallback(async (albumId: string) => {
setIsLoading(true);
try {
const { album, songs } = await api.getAlbum(albumId);
const tracks = songs.map(songToTrack);
// Clear the queue and set the new tracks
setQueue(tracks.slice(1)); // All tracks except the first one
// Play the first track immediately
if (tracks.length > 0) {
playTrack(tracks[0]);
if (shuffle) {
// If shuffle is enabled, shuffle the tracks
const shuffledTracks = [...tracks];
// Fisher-Yates shuffle algorithm
for (let i = shuffledTracks.length - 1; i > 0; i--) {
const j = Math.floor(Math.random() * (i + 1));
[shuffledTracks[i], shuffledTracks[j]] = [shuffledTracks[j], shuffledTracks[i]];
}
// Play the first shuffled track and set the rest as queue
playTrack(shuffledTracks[0]);
setQueue(shuffledTracks.slice(1));
} else {
// Normal order: play first track and set the rest as queue
playTrack(tracks[0]);
setQueue(tracks.slice(1));
}
}
toast({
title: "Playing Album",
description: `Now playing "${album.name}"`,
description: `Now playing "${album.name}"${shuffle ? ' (shuffled)' : ''}`,
});
} catch (error) {
console.error('Failed to play album:', error);
@@ -242,7 +304,7 @@ export const AudioPlayerProvider: React.FC<{ children: React.ReactNode }> = ({ c
} finally {
setIsLoading(false);
}
}, [api, playTrack, songToTrack, toast]);
}, [api, playTrack, songToTrack, toast, shuffle]);
const playAlbumFromTrack = useCallback(async (albumId: string, startingSongId: string) => {
setIsLoading(true);
@@ -257,15 +319,28 @@ export const AudioPlayerProvider: React.FC<{ children: React.ReactNode }> = ({ c
throw new Error('Starting song not found in album');
}
// Clear the queue and set the remaining tracks after the starting track
setQueue(tracks.slice(startingIndex + 1));
// Play the starting track immediately
playTrack(tracks[startingIndex]);
if (shuffle) {
// If shuffle is enabled, create a shuffled queue but start with the selected track
const remainingTracks = [...tracks];
remainingTracks.splice(startingIndex, 1); // Remove the starting track
// Shuffle the remaining tracks
for (let i = remainingTracks.length - 1; i > 0; i--) {
const j = Math.floor(Math.random() * (i + 1));
[remainingTracks[i], remainingTracks[j]] = [remainingTracks[j], remainingTracks[i]];
}
setQueue(remainingTracks);
playTrack(tracks[startingIndex]);
} else {
// Normal order: set the remaining tracks after the starting track as queue
setQueue(tracks.slice(startingIndex + 1));
playTrack(tracks[startingIndex]);
}
toast({
title: "Playing Album",
description: `Playing "${album.name}" from "${tracks[startingIndex].name}"`,
description: `Playing "${album.name}" from "${tracks[startingIndex].name}"${shuffle ? ' (shuffled)' : ''}`,
});
} catch (error) {
console.error('Failed to play album from track:', error);
@@ -277,7 +352,7 @@ export const AudioPlayerProvider: React.FC<{ children: React.ReactNode }> = ({ c
} finally {
setIsLoading(false);
}
}, [api, playTrack, songToTrack, toast]);
}, [api, playTrack, songToTrack, toast, shuffle]);
const skipToTrackInQueue = useCallback((index: number) => {
if (index >= 0 && index < queue.length) {
@@ -290,8 +365,25 @@ export const AudioPlayerProvider: React.FC<{ children: React.ReactNode }> = ({ c
}, [queue, playTrack]);
const toggleShuffle = useCallback(() => {
setShuffle(prev => !prev);
}, []);
setShuffle(prev => {
const newShuffleState = !prev;
// If turning shuffle ON, shuffle the current queue
if (newShuffleState && queue.length > 0) {
setQueue(prevQueue => {
const shuffledQueue = [...prevQueue];
// Fisher-Yates shuffle algorithm
for (let i = shuffledQueue.length - 1; i > 0; i--) {
const j = Math.floor(Math.random() * (i + 1));
[shuffledQueue[i], shuffledQueue[j]] = [shuffledQueue[j], shuffledQueue[i]];
}
return shuffledQueue;
});
}
return newShuffleState;
});
}, [queue.length]);
const shuffleAllAlbums = useCallback(async () => {
setIsLoading(true);