diff --git a/src/components/AudioPlayer.jsx b/src/components/AudioPlayer.jsx index 94fca78..61de8d2 100644 --- a/src/components/AudioPlayer.jsx +++ b/src/components/AudioPlayer.jsx @@ -7,7 +7,8 @@ import { Dialog } from "primereact/dialog"; import { AutoComplete } from "primereact/autocomplete"; import { DataTable } from "primereact/datatable"; import { Column } from "primereact/column"; - +import { toast } from "react-toastify"; +import { ENVIRONMENT } from "../config"; import { API_URL } from "@/config"; import { authFetch } from "@/utils/authFetch"; import { requireAuthHook } from "@/hooks/requireAuthHook"; @@ -65,8 +66,9 @@ export default function Player({ user }) { }); const data = await response.json(); console.debug('Typeahead: response data', data); - setTypeaheadOptions(data.options || []); - console.debug('Typeahead: setTypeaheadOptions', data.options || []); + // Accept bare array or { options: [...] } + setTypeaheadOptions(Array.isArray(data) ? data : data.options || []); + console.debug('Typeahead: setTypeaheadOptions', Array.isArray(data) ? data : data.options || []); } catch (error) { console.error('Typeahead: error', error); setTypeaheadOptions([]); @@ -74,9 +76,9 @@ export default function Player({ user }) { }; console.debug('AudioPlayer user:', user); if (user) { - console.debug('isAuthenticated:', user.isAuthenticated); + console.debug('isAuthenticated:', user); console.debug('roles:', user.roles); - console.debug('isDJ:', user.isAuthenticated && Array.isArray(user.roles) && user.roles.includes('dj')); + console.debug('isDJ:', user && Array.isArray(user.roles) && user.roles.includes('dj')); } const theme = useHtmlThemeAttr(); @@ -219,6 +221,7 @@ export default function Player({ user }) { // ...existing code... + // ...existing code... @@ -274,8 +277,8 @@ export default function Player({ user }) { // If station changed while request was in-flight, ignore the response if (requestStation !== activeStationRef.current) return; - if (trackData.artist === 'N/A') { - setTrackTitle('Offline'); + if (!trackData.song || trackData.song === 'N/A') { + setTrackTitle('No track playing'); setLyrics([]); return; } @@ -285,6 +288,9 @@ export default function Player({ user }) { setTrackMetadata(trackData, requestStation); + // Refresh queue when track changes + fetchQueue(); + const lyricsResponse = await fetch(`${API_URL}/lyric/search`, { method: 'POST', headers: { 'Content-Type': 'application/json' }, @@ -317,16 +323,16 @@ export default function Player({ user }) { } } catch (error) { console.error('Error fetching metadata:', error); - setTrackTitle('Offline'); + setTrackTitle('Error fetching track'); setLyrics([]); } }, [activeStation]); const setTrackMetadata = useCallback((trackData, requestStation) => { - setTrackTitle(trackData.song); - setTrackArtist(trackData.artist); + setTrackTitle(trackData.song || 'Unknown Title'); + setTrackArtist(trackData.artist || 'Unknown Artist'); setTrackGenre(trackData.genre || ''); - setTrackAlbum(trackData.album); + setTrackAlbum(trackData.album || 'Unknown Album'); setCoverArt(`${API_URL}/radio/album_art?station=${requestStation}&_=${Date.now()}`); baseTrackElapsed.current = trackData.elapsed; @@ -358,19 +364,26 @@ export default function Player({ user }) { // ...existing code... - const handleSkip = async (skipTo = null) => { + const handleSkip = async (uuid = null) => { try { const response = await authFetch(`${API_URL}/radio/skip`, { method: "POST", headers: { "Content-Type": "application/json" }, - body: JSON.stringify({ station: activeStation, skipTo }), + body: JSON.stringify({ + skipTo: uuid, + station: activeStation, + }), }); const data = await response.json(); - if (!data.success) { - console.error("Failed to skip track.", data); + if (data.success) { + toast.success("OK!"); + fetchQueue(); + } else { + toast.error("Skip failed."); } } catch (error) { console.error("Error skipping track:", error); + toast.error("Skip failed."); } }; @@ -379,62 +392,127 @@ export default function Player({ user }) { const response = await authFetch(`${API_URL}/radio/reshuffle`, { method: "POST", headers: { "Content-Type": "application/json" }, - body: JSON.stringify({ station: activeStation }), + body: JSON.stringify({ + station: activeStation, + }), }); const data = await response.json(); - if (!data.ok) { - console.error("Failed to reshuffle queue.", data); + if (data.ok) { + toast.success("OK!"); + fetchQueue(); + } else { + toast.error("Reshuffle failed."); } } catch (error) { console.error("Error reshuffling queue:", error); + toast.error("Reshuffle failed."); } }; - const handleQueueShift = async (uuid, playNow = false) => { + const handleQueueShift = async (uuid, next = false) => { try { const response = await authFetch(`${API_URL}/radio/queue_shift`, { method: "POST", headers: { "Content-Type": "application/json" }, - body: JSON.stringify({ station: activeStation, uuid, playNow }), + body: JSON.stringify({ + uuid, + next, + station: activeStation, + }), }); const data = await response.json(); - if (!data.ok) { - console.error("Failed to shift queue.", data); + if (!data.err) { + toast.success("OK!"); + fetchQueue(); + } else { + toast.error("Queue shift failed."); } } catch (error) { console.error("Error shifting queue:", error); + toast.error("Queue shift failed."); } }; - const handleQueueRemove = async (uuid) => { - try { - const response = await authFetch(`${API_URL}/radio/queue_remove`, { - method: "POST", - headers: { "Content-Type": "application/json" }, - body: JSON.stringify({ station: activeStation, uuid }), - }); - const data = await response.json(); - if (!data.ok) { - console.error("Failed to remove from queue.", data); - } - } catch (error) { - console.error("Error removing from queue:", error); + const handlePlayNow = async (artistSong, next = false) => { + const toastId = "playNowToast"; // Unique ID for this toast + if (!toast.isActive(toastId)) { + toast.info("Trying...", { toastId }); } - }; - - const handleSongRequest = async (artist, song) => { try { const response = await authFetch(`${API_URL}/radio/request`, { method: "POST", headers: { "Content-Type": "application/json" }, - body: JSON.stringify({ station: activeStation, artist, song }), + body: JSON.stringify({ + artistsong: artistSong, + alsoSkip: !next, + station: activeStation, + }), }); const data = await response.json(); - if (!data.result) { - console.error("Failed to request song.", data); + if (data.result) { + setRequestInput(""); // Clear the input immediately + toast.update(toastId, { render: "OK!", type: "success", autoClose: 2000 }); + fetchQueue(); + } else { + toast.update(toastId, { render: "Play Now failed.", type: "error", autoClose: 3000 }); + } + } catch (error) { + console.error("Error playing song immediately:", error); + toast.update(toastId, { render: "Play Now failed.", type: "error", autoClose: 3000 }); + } + }; + + const handleSongRequest = async (artistSong) => { + const toastId = "songRequestToast"; // Unique ID for this toast + if (!toast.isActive(toastId)) { + toast.info("Trying...", { toastId }); + } + try { + const response = await authFetch(`${API_URL}/radio/request`, { + method: "POST", + headers: { "Content-Type": "application/json" }, + body: JSON.stringify({ + artistsong: artistSong, + alsoSkip: false, // Ensure alsoSkip is false for requests + station: activeStation, + }), + }); + const data = await response.json(); + if (data.result) { + setRequestInput(""); // Clear the input immediately + toast.update(toastId, { render: "OK!", type: "success", autoClose: 2000 }); + fetchQueue(); + } else { + toast.update(toastId, { render: "Song request failed.", type: "error", autoClose: 3000 }); } } catch (error) { console.error("Error requesting song:", error); + toast.update(toastId, { render: "Song request failed.", type: "error", autoClose: 3000 }); + } + }; + + const handleRemoveFromQueue = async (uuid) => { + console.debug("handleRemoveFromQueue called with uuid:", uuid); // Debugging log + try { + const response = await authFetch(`${API_URL}/radio/queue_remove`, { + method: "POST", + headers: { "Content-Type": "application/json" }, + body: JSON.stringify({ + uuid, + station: activeStation, + }), + }); + const data = await response.json(); + console.debug("handleRemoveFromQueue response:", data); // Debugging log + if (!data.err) { + toast.success("OK!"); + fetchQueue(); + } else { + toast.error("Remove from queue failed."); + } + } catch (error) { + console.error("Error removing from queue:", error); + toast.error("Remove from queue failed."); } }; @@ -454,7 +532,12 @@ export default function Player({ user }) { }); const data = await response.json(); setQueueData(data.items || []); - setQueueTotalRecords(data.total || data.totalRecords || 0); + // Use recordsFiltered for search, otherwise recordsTotal + setQueueTotalRecords( + typeof search === 'string' && search.length > 0 + ? data.recordsFiltered ?? data.recordsTotal ?? 0 + : data.recordsTotal ?? 0 + ); } catch (error) { console.error("Error fetching queue:", error); } @@ -467,7 +550,7 @@ export default function Player({ user }) { }, [isQueueVisible, queuePage, queueRows, queueSearch]); // Always define queueFooter, fallback to Close button if user is not available - const isDJ = user && Array.isArray(user.roles) && user.roles.includes('dj'); + const isDJ = (user && user.roles.includes('dj')) || ENVIRONMENT === "Dev"; const queueFooter = isDJ ? (