import React, { useState, useEffect, useRef, Suspense, lazy, useMemo } from "react"; import { toast } from "react-toastify"; import { Button } from "@mui/joy"; import { Accordion, AccordionTab } from "primereact/accordion"; import { AutoComplete } from "primereact/autocomplete"; import { authFetch } from "@/utils/authFetch"; import BreadcrumbNav from "./BreadcrumbNav"; import { API_URL, ENVIRONMENT } from "@/config"; export default function MediaRequestForm() { const [type, setType] = useState("artist"); const [selectedArtist, setSelectedArtist] = useState(null); const [artistInput, setArtistInput] = useState(""); const [albumInput, setAlbumInput] = useState(""); const [trackInput, setTrackInput] = useState(""); const [quality, setQuality] = useState("FLAC"); // default FLAC const [selectedItem, setSelectedItem] = useState(null); const [albums, setAlbums] = useState([]); const [tracksByAlbum, setTracksByAlbum] = useState({}); const [selectedTracks, setSelectedTracks] = useState({}); const [artistSuggestions, setArtistSuggestions] = useState([]); const [isSubmitting, setIsSubmitting] = useState(false); const [isSearching, setIsSearching] = useState(false); const [loadingAlbumId, setLoadingAlbumId] = useState(null); const [expandedAlbums, setExpandedAlbums] = useState([]); const [isFetching, setIsFetching] = useState(false); const [currentTrackId, setCurrentTrackId] = useState(null); const [isAudioPlaying, setIsAudioPlaying] = useState(false); const [audioLoadingTrackId, setAudioLoadingTrackId] = useState(null); const [playbackQueue, setPlaybackQueue] = useState([]); const [queueIndex, setQueueIndex] = useState(null); const [queueAlbumId, setQueueAlbumId] = useState(null); const [albumPlaybackLoadingId, setAlbumPlaybackLoadingId] = useState(null); const [shuffleAlbums, setShuffleAlbums] = useState({}); const [audioProgress, setAudioProgress] = useState({ current: 0, duration: 0 }); const debounceTimeout = useRef(null); const autoCompleteRef = useRef(null); const metadataFetchToastId = useRef(null); const audioRef = useRef(null); const audioSourcesRef = useRef({}); const pendingTrackFetchesRef = useRef({}); const playbackQueueRef = useRef([]); const queueIndexRef = useRef(null); const queueAlbumIdRef = useRef(null); const albumHeaderRefs = useRef({}); const suppressHashRef = useRef(false); const lastUrlRef = useRef(""); const delay = (ms) => new Promise((resolve) => setTimeout(resolve, ms)); // Helper for delays const sanitizeFilename = (text) => (text || "").replace(/[\\/:*?"<>|]/g, "_") || "track"; const formatTime = (seconds) => { if (!Number.isFinite(seconds) || seconds < 0) return "0:00"; const mins = Math.floor(seconds / 60); const secs = Math.floor(seconds % 60) .toString() .padStart(2, "0"); return `${mins}:${secs}`; }; useEffect(() => { if (typeof window === "undefined") return; lastUrlRef.current = window.location.pathname + window.location.search + window.location.hash; }, []); useEffect(() => { if (typeof window === "undefined") return undefined; const handleHashChange = () => { if (suppressHashRef.current) { const fallback = lastUrlRef.current || window.location.pathname + window.location.search; if (typeof window.history?.replaceState === "function") { window.history.replaceState(null, "", fallback); } lastUrlRef.current = fallback; suppressHashRef.current = false; } else { lastUrlRef.current = window.location.pathname + window.location.search + window.location.hash; } }; window.addEventListener("hashchange", handleHashChange); return () => window.removeEventListener("hashchange", handleHashChange); }, []); const Spinner = () => ( ); const InlineSpinner = ({ sizeClass = "h-3 w-3" }) => ( ); const PlayIcon = () => ( ); const PauseIcon = () => ( ); const ShuffleIcon = ({ active }) => ( ); const shuffleArray = (arr) => { const clone = [...arr]; for (let i = clone.length - 1; i > 0; i--) { const j = Math.floor(Math.random() * (i + 1)); [clone[i], clone[j]] = [clone[j], clone[i]]; } return clone; }; const ensureAlbumExpanded = (albumIndex) => { if (typeof albumIndex !== "number") return; let albumAdded = false; setExpandedAlbums((prev) => { const current = Array.isArray(prev) ? [...prev] : []; if (current.includes(albumIndex)) return prev; if (typeof window !== "undefined") { lastUrlRef.current = window.location.pathname + window.location.search + window.location.hash; } suppressHashRef.current = true; current.push(albumIndex); albumAdded = true; return current; }); if (albumAdded) { setTimeout(() => { suppressHashRef.current = false; }, 400); } }; const resetQueueState = () => { setPlaybackQueue([]); playbackQueueRef.current = []; setQueueIndex(null); queueIndexRef.current = null; setQueueAlbumId(null); queueAlbumIdRef.current = null; setAlbumPlaybackLoadingId(null); setAudioProgress({ current: 0, duration: 0 }); }; const getTrackSource = async (trackId) => { if (audioSourcesRef.current[trackId]) { return audioSourcesRef.current[trackId]; } if (!pendingTrackFetchesRef.current[trackId]) { pendingTrackFetchesRef.current[trackId] = (async () => { const res = await authFetch(`${API_URL}/trip/get_track_by_id/${trackId}?quality=${quality}`); if (!res.ok) throw new Error("Failed to fetch track URL"); const data = await res.json(); if (!data.stream_url) { throw new Error("No stream URL returned for this track."); } const fileResponse = await fetch(data.stream_url, { method: "GET", mode: "cors", credentials: "omit", }); if (!fileResponse.ok) { throw new Error(`Failed to fetch track file: ${fileResponse.status}`); } const blob = await fileResponse.blob(); const sourceUrl = URL.createObjectURL(blob); if (audioSourcesRef.current[trackId]) { URL.revokeObjectURL(audioSourcesRef.current[trackId]); } audioSourcesRef.current[trackId] = sourceUrl; return sourceUrl; })().finally(() => { delete pendingTrackFetchesRef.current[trackId]; }); } return pendingTrackFetchesRef.current[trackId]; }; const prefetchTrack = async (track) => { if (!track || audioSourcesRef.current[track.id] || pendingTrackFetchesRef.current[track.id]) { return; } try { await getTrackSource(track.id); } catch (error) { console.error("Prefetch failed", error); } }; const playTrack = async (track, { fromQueue = false } = {}) => { const audio = audioRef.current; if (!audio || !track) return; if (!fromQueue) { resetQueueState(); } setAudioLoadingTrackId(track.id); try { const sourceUrl = await getTrackSource(track.id); audio.pause(); audio.currentTime = 0; audio.src = sourceUrl; setAudioProgress({ current: 0, duration: 0 }); setCurrentTrackId(track.id); await audio.play(); } catch (error) { console.error(error); toast.error("Failed to play track."); if (!fromQueue) { resetQueueState(); } } finally { setAudioLoadingTrackId(null); } }; // Fetch artist suggestions for autocomplete const searchArtists = (e) => { const query = e.query.trim(); if (!query) { setArtistSuggestions([]); setSelectedArtist(null); return; } if (debounceTimeout.current) clearTimeout(debounceTimeout.current); debounceTimeout.current = setTimeout(async () => { try { // Ensure at least 600ms between actual requests const now = Date.now(); if (!searchArtists.lastCall) searchArtists.lastCall = 0; const elapsed = now - searchArtists.lastCall; const minDelay = 600; // ms if (elapsed < minDelay) await delay(minDelay - elapsed); searchArtists.lastCall = Date.now(); const res = await authFetch( `${API_URL}/trip/get_artists_by_name?artist=${encodeURIComponent(query)}&group=true` ); if (!res.ok) throw new Error("API error"); const data = await res.json(); setArtistSuggestions(data); } catch (err) { toast.error("Failed to fetch artist suggestions."); setArtistSuggestions([]); } }, 500); // debounce 500ms }; const truncate = (text, maxLen) => maxLen <= 3 ? text.slice(0, maxLen) : text.length <= maxLen ? text : text.slice(0, maxLen - 3) + '...'; const selectArtist = (artist) => { // artist may be a grouped item or an alternate object const value = artist.artist || artist.name || ""; setSelectedArtist(artist); setArtistInput(value); setArtistSuggestions([]); // Hide autocomplete panel and blur input to prevent leftover white box. // Use a small timeout so PrimeReact internal handlers can finish first. try { const ac = autoCompleteRef.current; // Give PrimeReact a tick to finish its events, then call hide(). setTimeout(() => { try { if (ac && typeof ac.hide === "function") { ac.hide(); } // If panel still exists, hide it via style (safer than removing the node) setTimeout(() => { const panel = document.querySelector('.p-autocomplete-panel'); if (panel) { panel.style.display = 'none'; panel.style.opacity = '0'; panel.style.visibility = 'hidden'; panel.style.pointerEvents = 'none'; } }, 10); // blur the input element if present const inputEl = ac?.getInput ? ac.getInput() : document.querySelector('.p-autocomplete-input'); if (inputEl && typeof inputEl.blur === 'function') inputEl.blur(); } catch (innerErr) { // Ignore inner errors } }, 0); } catch (err) { // Ignore outer errors } }; const totalAlbums = albums.length; const totalTracks = useMemo(() => Object.values(tracksByAlbum).reduce((sum, arr) => (Array.isArray(arr) ? sum + arr.length : sum), 0), [tracksByAlbum] ); const artistItemTemplate = (artist) => { if (!artist) return null; const alts = artist.alternatives || artist.alternates || []; return (
{truncate(artist.artist || artist.name, 58)} ID: {artist.id}
{alts.length > 0 && (
Alternates: {alts.map((alt, idx) => ( ID: {alt.id} {idx < alts.length - 1 ? , : null} ))}
)}
); }; // Handle autocomplete input changes (typing/selecting) const handleArtistChange = (e) => { if (typeof e.value === "string") { setArtistInput(e.value); setSelectedArtist(null); } else if (e.value && typeof e.value === "object") { setSelectedArtist(e.value); setArtistInput(e.value.artist); } else { setArtistInput(""); setSelectedArtist(null); } }; // Search button click handler const handleSearch = async () => { toast.dismiss(); setIsSearching(true); resetQueueState(); setShuffleAlbums({}); if (audioRef.current) { audioRef.current.pause(); audioRef.current.removeAttribute("src"); audioRef.current.load(); } setIsAudioPlaying(false); setAudioLoadingTrackId(null); setCurrentTrackId(null); try { if (metadataFetchToastId.current) toast.dismiss(metadataFetchToastId.current); } catch (err) { } metadataFetchToastId.current = toast.info("Retrieving metadata...", { autoClose: false, progress: 0, closeOnClick: false, } ); if (type === "artist") { if (!selectedArtist) { toast.error("Please select a valid artist from suggestions."); setIsSearching(false); return; } setSelectedItem(selectedArtist.artist); try { const res = await authFetch( `${API_URL}/trip/get_albums_by_artist_id/${selectedArtist.id}?quality=${quality}` ); if (!res.ok) throw new Error("API error"); const data = await res.json(); data.sort((a, b) => (b.release_date || "").localeCompare(a.release_date || "") ); setAlbums(data); setShuffleAlbums({}); setTracksByAlbum({}); setExpandedAlbums([]); // Set selectedTracks for all albums as null (means tracks loading/not loaded) setSelectedTracks( data.reduce((acc, album) => { acc[album.id] = null; return acc; }, {}) ); } catch (err) { toast.error("Failed to fetch albums for artist."); setAlbums([]); setTracksByAlbum({}); setSelectedTracks({}); } } else if (type === "album") { if (!artistInput.trim() || !albumInput.trim()) { toast.error("Artist and Album are required."); setIsSearching(false); return; } setSelectedItem(`${artistInput} - ${albumInput}`); setAlbums([]); setTracksByAlbum({}); setSelectedTracks({}); } else if (type === "track") { if (!artistInput.trim() || !trackInput.trim()) { toast.error("Artist and Track are required."); setIsSearching(false); return; } setSelectedItem(`${artistInput} - ${trackInput}`); setAlbums([]); setTracksByAlbum({}); setSelectedTracks({}); } setIsSearching(false); }; const handleTrackPlayPause = async (track, albumId = null, albumIndex = null) => { const audio = audioRef.current; if (!audio) return; if (typeof albumIndex === "number") { ensureAlbumExpanded(albumIndex); } if (currentTrackId === track.id) { if (audio.paused) { try { await audio.play(); } catch (error) { console.error(error); toast.error("Unable to resume playback."); } } else { audio.pause(); } return; } await playTrack(track, { fromQueue: false }); }; const toggleAlbumShuffle = (albumId) => { setShuffleAlbums((prev) => ({ ...prev, [albumId]: !prev[albumId] })); }; const startAlbumPlayback = async (albumId, albumIndex) => { const tracks = tracksByAlbum[albumId]; if (!Array.isArray(tracks) || tracks.length === 0) { toast.error("Tracks are still loading for this album."); return; } ensureAlbumExpanded(albumIndex); const shouldShuffle = !!shuffleAlbums[albumId]; const queue = shouldShuffle ? shuffleArray(tracks) : [...tracks]; setPlaybackQueue(queue); playbackQueueRef.current = queue; setQueueAlbumId(albumId); queueAlbumIdRef.current = albumId; setQueueIndex(0); queueIndexRef.current = 0; setAlbumPlaybackLoadingId(albumId); try { await playTrack(queue[0], { fromQueue: true }); setAlbumPlaybackLoadingId(null); if (queue[1]) prefetchTrack(queue[1]); } catch (err) { setAlbumPlaybackLoadingId(null); } }; const handleAlbumPlayPause = async (albumId, albumIndex) => { const audio = audioRef.current; if (!audio) return; ensureAlbumExpanded(albumIndex); if (queueAlbumId === albumId && playbackQueue.length > 0) { if (audio.paused) { try { await audio.play(); } catch (error) { console.error(error); toast.error("Unable to resume album playback."); } } else { audio.pause(); } return; } await startAlbumPlayback(albumId, albumIndex); }; const handleTrackDownload = async (track) => { try { const res = await authFetch(`${API_URL}/trip/get_track_by_id/${track.id}?quality=${quality}`); if (!res.ok) throw new Error("Failed to fetch track URL"); const data = await res.json(); if (!data.stream_url) { throw new Error("No stream URL returned for this track."); } const fileResponse = await fetch(data.stream_url, { method: "GET", mode: "cors", credentials: "omit", }); if (!fileResponse.ok) { throw new Error(`Failed to fetch track file: ${fileResponse.status}`); } const blob = await fileResponse.blob(); const url = URL.createObjectURL(blob); const artistName = track.artist || selectedArtist?.artist || "Unknown Artist"; const urlPath = new URL(data.stream_url).pathname; const extension = urlPath.split(".").pop().split("?")[0] || "flac"; const filename = `${sanitizeFilename(artistName)} - ${sanitizeFilename(track.title)}.${extension}`; const link = document.createElement("a"); link.href = url; link.download = filename; document.body.appendChild(link); link.click(); link.remove(); URL.revokeObjectURL(url); } catch (error) { console.error(error); toast.error("Failed to download track."); } }; useEffect(() => { if (typeof Audio === "undefined") { return undefined; } const audio = new Audio(); audio.preload = "auto"; audioRef.current = audio; const handleEnded = async () => { const queue = playbackQueueRef.current; const index = queueIndexRef.current; if (Array.isArray(queue) && queue.length > 0 && index !== null && index + 1 < queue.length) { const nextIndex = index + 1; setQueueIndex(nextIndex); queueIndexRef.current = nextIndex; setAlbumPlaybackLoadingId(queueAlbumIdRef.current); try { await playTrack(queue[nextIndex], { fromQueue: true }); setAlbumPlaybackLoadingId(null); const upcoming = queue[nextIndex + 1]; if (upcoming) prefetchTrack(upcoming); } catch (error) { console.error("Failed to advance queue", error); setAlbumPlaybackLoadingId(null); resetQueueState(); } return; } setIsAudioPlaying(false); setCurrentTrackId(null); resetQueueState(); }; const updateProgress = () => { setAudioProgress({ current: audio.currentTime || 0, duration: audio.duration || 0 }); }; const handlePause = () => { setIsAudioPlaying(false); updateProgress(); }; const handlePlay = () => { setIsAudioPlaying(true); updateProgress(); }; const handleTimeUpdate = () => { updateProgress(); const queue = playbackQueueRef.current; const index = queueIndexRef.current; if (!Array.isArray(queue) || queue.length === 0 || index === null) return; const nextTrack = queue[index + 1]; if (!nextTrack) return; const duration = audio.duration || 0; const currentTime = audio.currentTime || 0; const progress = duration > 0 ? currentTime / duration : 0; if ((duration > 0 && progress >= 0.7) || (duration === 0 && currentTime >= 15)) { prefetchTrack(nextTrack); } }; audio.addEventListener("ended", handleEnded); audio.addEventListener("pause", handlePause); audio.addEventListener("play", handlePlay); audio.addEventListener("timeupdate", handleTimeUpdate); audio.addEventListener("loadedmetadata", updateProgress); audio.addEventListener("seeking", updateProgress); audio.addEventListener("seeked", updateProgress); return () => { audio.pause(); audio.removeAttribute("src"); audio.load(); audio.removeEventListener("ended", handleEnded); audio.removeEventListener("pause", handlePause); audio.removeEventListener("play", handlePlay); audio.removeEventListener("timeupdate", handleTimeUpdate); audio.removeEventListener("loadedmetadata", updateProgress); audio.removeEventListener("seeking", updateProgress); audio.removeEventListener("seeked", updateProgress); Object.values(audioSourcesRef.current).forEach((url) => URL.revokeObjectURL(url)); audioSourcesRef.current = {}; }; }, []); useEffect(() => { playbackQueueRef.current = playbackQueue; }, [playbackQueue]); useEffect(() => { queueIndexRef.current = queueIndex; }, [queueIndex]); useEffect(() => { queueAlbumIdRef.current = queueAlbumId; }, [queueAlbumId]); useEffect(() => { Object.values(audioSourcesRef.current).forEach((url) => URL.revokeObjectURL(url)); audioSourcesRef.current = {}; pendingTrackFetchesRef.current = {}; if (audioRef.current) { audioRef.current.pause(); audioRef.current.removeAttribute("src"); audioRef.current.load(); } setIsAudioPlaying(false); setAudioLoadingTrackId(null); setCurrentTrackId(null); resetQueueState(); }, [quality]); const allTracksLoaded = albums.every(({ id }) => Array.isArray(tracksByAlbum[id]) && tracksByAlbum[id].length > 0); const handleToggleAllAlbums = () => { const allSelected = albums.every(({ id }) => { const allTracks = tracksByAlbum[id] || []; return selectedTracks[id]?.length === allTracks.length && allTracks.length > 0; }); const newSelection = {}; albums.forEach(({ id }) => { const allTracks = tracksByAlbum[id] || []; if (allSelected) { // Uncheck all newSelection[id] = []; } else { // Check all tracks in the album newSelection[id] = allTracks.map(track => String(track.id)); } }); setSelectedTracks(newSelection); }; { e.preventDefault(); if (!allTracksLoaded) return; // prevent clicking before data ready handleToggleAllAlbums(); }} className={`text-sm hover:underline cursor-pointer ${!allTracksLoaded ? "text-gray-400 dark:text-gray-500 pointer-events-none" : "text-blue-600" }`} > Check / Uncheck All Albums // Sequentially fetch tracks for albums not loaded yet useEffect(() => { if (type !== "artist" || albums.length === 0) return; let isCancelled = false; const albumsToFetch = albums.filter((a) => !tracksByAlbum[a.id]); if (albumsToFetch.length === 0) return; const fetchTracksSequentially = async () => { const minDelay = 650; // ms between API requests setIsFetching(true); const totalAlbums = albumsToFetch.length; for (let index = 0; index < totalAlbums; index++) { const album = albumsToFetch[index]; if (isCancelled) break; setLoadingAlbumId(album.id); try { const now = Date.now(); if (!fetchTracksSequentially.lastCall) fetchTracksSequentially.lastCall = 0; const elapsed = now - fetchTracksSequentially.lastCall; if (elapsed < minDelay) await delay(minDelay - elapsed); fetchTracksSequentially.lastCall = Date.now(); const res = await authFetch(`${API_URL}/trip/get_tracks_by_album_id/${album.id}`); if (!res.ok) throw new Error("API error"); const data = await res.json(); if (isCancelled) break; setTracksByAlbum((prev) => ({ ...prev, [album.id]: data })); setSelectedTracks((prev) => ({ ...prev, [album.id]: data.map((t) => String(t.id)), })); } catch (err) { toast.error(`Failed to fetch tracks for album ${album.album}.`); setTracksByAlbum((prev) => ({ ...prev, [album.id]: [] })); setSelectedTracks((prev) => ({ ...prev, [album.id]: [] })); } // Update progress toast toast.update(metadataFetchToastId.current, { progress: (index + 1) / totalAlbums, render: `Retrieving metadata... (${index + 1} / ${totalAlbums})`, }); } setLoadingAlbumId(null); setIsFetching(false); // Finish the toast toast.update(metadataFetchToastId.current, { render: "Metadata retrieved!", type: "success", progress: 1, autoClose: 1500, }); }; fetchTracksSequentially(); return () => { isCancelled = true; }; }, [albums, type]); // Toggle individual track checkbox const toggleTrack = (albumId, trackId) => { setSelectedTracks((prev) => { const current = new Set(prev[albumId] || []); if (current.has(String(trackId))) current.delete(String(trackId)); else current.add(String(trackId)); return { ...prev, [albumId]: Array.from(current) }; }); }; // Toggle album checkbox (select/deselect all tracks in album) const toggleAlbum = (albumId) => { const allTracks = tracksByAlbum[albumId]?.map((t) => String(t.id)) || []; setSelectedTracks((prev) => { const current = prev[albumId] || []; const allSelected = current.length === allTracks.length; return { ...prev, [albumId]: allSelected ? [] : [...allTracks], }; }); }; // Attach scroll fix for autocomplete panel const attachScrollFix = () => { setTimeout(() => { const panel = document.querySelector(".p-autocomplete-panel"); const items = panel?.querySelector(".p-autocomplete-items"); 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); }; // Submit request handler with progress indicator const handleSubmitRequest = async () => { if (isFetching) { // tracks are not done being fetched return toast.error("Still fetching track metadata, please wait a moment."); } setIsSubmitting(true); try { const allSelectedIds = Object.values(selectedTracks) .filter(arr => Array.isArray(arr)) // skip null entries .flat(); const response = await authFetch(`${API_URL}/trip/bulk_fetch`, { method: "POST", headers: { "Content-Type": "application/json; charset=utf-8", }, body: JSON.stringify({ track_ids: allSelectedIds, target: selectedArtist.artist, quality: quality, }), }); if (!response.ok) { throw new Error(`Server error: ${response.status}`); } const data = await response.json(); toast.success(`Request submitted! (${allSelectedIds.length} tracks)`); } catch (err) { console.error(err); toast.error("Failed to submit request."); } finally { setIsSubmitting(false); } }; return (

New Request

{(type === "album" || type === "track") && ( type === "album" ? setAlbumInput(e.target.value) : setTrackInput(e.target.value) } placeholder={type === "album" ? "Album" : "Track"} /> )}
{type === "artist" && albums.length > 0 && ( <> setExpandedAlbums(e.index)} > {albums.map(({ album, id, release_date }, albumIndex) => { const allTracks = tracksByAlbum[id] || []; const selected = selectedTracks[id]; // Album checkbox is checked if tracks not loaded (selected === null) // or all tracks loaded and all selected const allChecked = selected === null || (selected?.length === allTracks.length && allTracks.length > 0); const someChecked = selected !== null && selected.length > 0 && selected.length < allTracks.length; return ( { if (el) el.indeterminate = someChecked; }} onChange={() => toggleAlbum(id)} onClick={(e) => e.stopPropagation()} className="trip-checkbox cursor-pointer" aria-label={`Select all tracks for album ${album}`} />
e.stopPropagation()}>
{truncate(album, 32)} {loadingAlbumId === id && } ({release_date}) {typeof tracksByAlbum[id] === 'undefined' ? ( loadingAlbumId === id ? 'Loading...' : '...' ) : ( `${allTracks.length} track${allTracks.length !== 1 ? 's' : ''}` )}
} > {allTracks.length > 0 ? (
    {allTracks.map((track) => { const isCurrentTrack = currentTrackId === track.id; const showProgress = isCurrentTrack && audioProgress.duration > 0; const safeProgress = { current: Math.min(audioProgress.current, audioProgress.duration || 0), duration: audioProgress.duration || 0, }; return (
  • toggleTrack(id, track.id)} className="trip-checkbox cursor-pointer" aria-label={`Select track ${track.title} `} />
    {truncate(track.title, 80)} {track.version && ( {track.version} )}
    {quality} {track.duration && ( {track.duration} )}
    {showProgress && (
    { if (!audioRef.current) return; const nextValue = Number(e.target.value); audioRef.current.currentTime = nextValue; setAudioProgress((prev) => ({ ...prev, current: nextValue })); }} className="w-full h-1 cursor-pointer accent-blue-600" aria-label={`Seek within ${track.title}`} />
    {formatTime(safeProgress.current)} {formatTime(safeProgress.duration)}
    )}
  • ); })}
) : (
{tracksByAlbum[id] ? "No tracks found for this album." : "Loading tracks..."}
)} ); })}
) }
); }