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 ? (
@@ -514,7 +597,7 @@ export default function Player({ user }) {
- {user && Hello, {user.user}} + {/* {user && Hello, {user.user}} */}
@@ -554,69 +637,74 @@ export default function Player({ user }) {
{isDJ && ( -
- { - console.debug('AutoComplete: completeMethod called', e); - fetchTypeahead(e.query); - }} - onChange={e => { - console.debug('AutoComplete: onChange', e); - setRequestInput(e.target.value); - }} - onShow={() => { - setTimeout(() => { - const panel = document.querySelector('.p-autocomplete-panel'); - const items = panel?.querySelector('.p-autocomplete-items'); - console.debug('AutoComplete: onShow panel', panel); - if (items) { - items.style.maxHeight = '200px'; - items.style.overflowY = 'auto'; - items.style.overscrollBehavior = 'contain'; - const wheelHandler = (e) => { - const delta = e.deltaY; - const atTop = items.scrollTop === 0; - const atBottom = items.scrollTop + items.clientHeight >= items.scrollHeight; - if ((delta < 0 && atTop) || (delta > 0 && atBottom)) { - e.preventDefault(); - } else { - e.stopPropagation(); - } - }; - items.removeEventListener('wheel', wheelHandler); - items.addEventListener('wheel', wheelHandler, { passive: false }); - } - }, 0); - }} - placeholder="Request a song..." - inputStyle={{ - width: '14rem', - padding: '0.25rem 0.5rem', - borderRadius: '9999px', - border: '1px solid #d1d5db', - fontSize: '0.85rem', - fontWeight: 'bold', - }} - className="typeahead-input" - forceSelection={false} - /> - - - - +
+
+ { + console.debug('AutoComplete: completeMethod called', e); + fetchTypeahead(e.query); + }} + onChange={e => { + console.debug('AutoComplete: onChange', e); + setRequestInput(e.target.value); + }} + onShow={() => { + setTimeout(() => { + const panel = document.querySelector('.p-autocomplete-panel'); + const items = panel?.querySelector('.p-autocomplete-items'); + console.debug('AutoComplete: onShow panel', panel); + if (items) { + items.style.maxHeight = '200px'; + items.style.overflowY = 'auto'; + items.style.overscrollBehavior = 'contain'; + const wheelHandler = (e) => { + const delta = e.deltaY; + const atTop = items.scrollTop === 0; + const atBottom = items.scrollTop + items.clientHeight >= items.scrollHeight; + if ((delta < 0 && atTop) || (delta > 0 && atBottom)) { + e.preventDefault(); + } else { + e.stopPropagation(); + } + }; + items.removeEventListener('wheel', wheelHandler); + items.addEventListener('wheel', wheelHandler, { passive: false }); + } + }, 0); + }} + placeholder="Request a song..." + inputStyle={{ + width: '24rem', + minWidth: '24rem', + padding: '0.35rem 0.75rem', + borderRadius: '9999px', + border: '1px solid #d1d5db', + fontSize: '1rem', + fontWeight: 'bold', + }} + className="typeahead-input" + forceSelection={false} + /> +
+ + + + +
+
)} {/* Always show play/pause button */} @@ -646,12 +734,13 @@ export default function Player({ user }) {