import { useState, useEffect, useRef } from "react"; import Hls from "hls.js"; import { metaData } from "../config"; import Play from "@mui/icons-material/PlayArrow"; import Pause from "@mui/icons-material/Pause"; import "@styles/player.css"; const API_URL = "https://api.codey.lol"; const STATIONS = { main: { label: "Main" }, rock: { label: "Rock" }, rap: { label: "Rap" }, electronic: { label: "Electronic" }, classical: { label: "Classical" }, pop: { label: "Pop" }, }; let activeInterval = null; let currentStationForInterval = null; function clearGlobalMetadataInterval() { if (activeInterval) { clearInterval(activeInterval); activeInterval = null; currentStationForInterval = null; } } export function Player() { const [activeStation, setActiveStation] = useState("main"); const [isPlaying, setIsPlaying] = useState(false); const [trackTitle, setTrackTitle] = useState(""); const [trackArtist, setTrackArtist] = useState(""); const [trackGenre, setTrackGenre] = useState(""); const [trackAlbum, setTrackAlbum] = useState(""); const [coverArt, setCoverArt] = useState("/images/radio_art_default.jpg"); const [elapsed, setElapsed] = useState(0); const [duration, setDuration] = useState(0); const audioRef = useRef(null); const hlsRef = useRef(null); const uuidRef = useRef(null); const lastStationData = useRef(null); const formatTime = (seconds) => { const m = String(Math.floor(seconds / 60)).padStart(2, "0"); const s = String(Math.floor(seconds % 60)).padStart(2, "0"); return `${m}:${s}`; }; const progress = duration > 0 ? (elapsed / duration) * 100 : 0; const playStream = () => { const lcStation = activeStation.toLowerCase(); const streamUrl = `https://stream.codey.lol/hls/${lcStation}/${lcStation}.m3u8?t=${Date.now()}`; // Cleanup existing stream if (hlsRef.current) { hlsRef.current.destroy(); hlsRef.current = null; } const audio = audioRef.current; if (!audio) return; if (audio.canPlayType("application/vnd.apple.mpegurl")) { // Native support (Safari) audio.src = streamUrl; audio.play().then(() => setIsPlaying(true)).catch(console.error); } else if (Hls.isSupported()) { const hls = new Hls({ maxBufferLength: 30, maxMaxBufferLength: 60, abrEwmaFastLive: 2.0, abrEwmaSlowLive: 6.0, abrBandWidthFactor: 0.95, // Bias toward higher quality autoStartLoad: true, startLevel: -1, // adaptive start }); hls.loadSource(streamUrl); hls.attachMedia(audio); hls.on(Hls.Events.ERROR, (_, data) => { if (data.fatal) { console.error("HLS fatal error:", data); hls.destroy(); } }); hlsRef.current = hls; audio.play().then(() => setIsPlaying(true)).catch(console.error); } else { console.error("HLS not supported"); } }; const togglePlayback = () => { const audio = audioRef.current; if (!audio) return; if (isPlaying) { audio.pause(); setIsPlaying(false); } else { audio.play().then(() => setIsPlaying(true)).catch(console.error); } }; useEffect(() => { const audio = audioRef.current; if (audio) { audio.pause(); audio.src = ""; setIsPlaying(false); } playStream(); return () => { if (hlsRef.current) { hlsRef.current.destroy(); hlsRef.current = null; } }; }, [activeStation]); useEffect(() => { clearGlobalMetadataInterval(); currentStationForInterval = activeStation; const setPageTitle = (artist, song) => { document.title = `${metaData.title} - Radio - ${artist} - ${song} [${activeStation}]`; }; const fetchTrackData = async () => { try { const response = await fetch(`${API_URL}/radio/np?station=${activeStation}`, { method: "POST", }); const data = await response.json(); if (currentStationForInterval !== activeStation) return; if (data.artist === "N/A" && data.song === "N/A") { if (lastStationData.current !== "offline") { setTrackTitle("Offline"); setTrackArtist(""); setTrackAlbum(""); setTrackGenre(""); setCoverArt("/images/radio_art_default.jpg"); setElapsed(0); setDuration(0); lastStationData.current = "offline"; } return; } if (data.uuid === uuidRef.current) { if (lastStationData.current === data.uuid) { setElapsed(data.elapsed); setDuration(data.duration); } return; } uuidRef.current = data.uuid; lastStationData.current = data.uuid; setTrackTitle(data.song); setTrackArtist(data.artist); setTrackGenre(data.genre !== "N/A" ? data.genre : ""); setTrackAlbum(data.album); setPageTitle(data.artist, data.song); setCoverArt(`${API_URL}/radio/album_art?station=${activeStation}&_=${Date.now()}`); setElapsed(data.elapsed); setDuration(data.duration); } catch (error) { console.error("Failed to fetch track data:", error); } }; fetchTrackData(); activeInterval = setInterval(fetchTrackData, 1000); return () => clearGlobalMetadataInterval(); }, [activeStation]); const remaining = duration - elapsed; const progressColorClass = progress >= 90 ? "bg-red-500 dark:bg-red-400" : progress >= 75 || remaining <= 20 ? "bg-yellow-400 dark:bg-yellow-300" : "bg-blue-500 dark:bg-blue-400"; return ( <>
{formatTime(elapsed)}
{formatTime(remaining)}