From 2ec5c3df5ebd7e8ec1d6d0a150feee398344ec6e Mon Sep 17 00:00:00 2001 From: ale Date: Fri, 30 May 2025 17:45:13 +0200 Subject: [PATCH] refactor AI Signed-off-by: ale --- package.json | 1 + src/App.js | 256 ++++++++++++------------------- src/components/AudioControls.js | 93 +++++++++++ src/components/Header.js | 32 ++++ src/components/TrackInfo.js | 35 +++++ src/hooks/useAudioPlayer.js | 107 +++++++++++++ src/hooks/useBackgroundImages.js | 39 +++++ src/hooks/useStreamData.js | 122 +++++++++++++++ 8 files changed, 525 insertions(+), 160 deletions(-) create mode 100644 src/components/AudioControls.js create mode 100644 src/components/Header.js create mode 100644 src/components/TrackInfo.js create mode 100644 src/hooks/useAudioPlayer.js create mode 100644 src/hooks/useBackgroundImages.js create mode 100644 src/hooks/useStreamData.js diff --git a/package.json b/package.json index da13a59..cc06b0d 100644 --- a/package.json +++ b/package.json @@ -8,6 +8,7 @@ "@testing-library/react": "^16.3.0", "@testing-library/user-event": "^13.5.0", "materialize-css": "^1.0.0", + "prop-types": "^15.8.1", "react": "^19.1.0", "react-dom": "^19.1.0", "react-materialize": "^3.10.0", diff --git a/src/App.js b/src/App.js index bb21933..a7ef4f6 100644 --- a/src/App.js +++ b/src/App.js @@ -1,175 +1,111 @@ import "./App.css"; -import { useCallback, useEffect, useRef, useState } from "react"; +import { useEffect, useRef } from "react"; import WaveForm from "./WaveForm"; import M from "materialize-css"; -import { Button, Icon, Range } from "react-materialize"; import text from "./list.txt"; +// Custom hooks +import useAudioPlayer from "./hooks/useAudioPlayer"; +import useStreamData from "./hooks/useStreamData"; +import useBackgroundImages from "./hooks/useBackgroundImages"; + +// Components +import Header from "./components/Header"; +import TrackInfo from "./components/TrackInfo"; +import AudioControls from "./components/AudioControls"; + +/** + * Main App component + */ const App = () => { - const [audioUrl, setAudioUrl] = useState(null), - [analyzerData, setAnalyzerData] = useState(null), - [bounce, setBounce] = useState(''), - [json, setJson] = useState({}), - [currentVolume, setCurrentVolume] = useState(0.5), - [muted, setMuted] = useState(false), - [link, setLink] = useState(''), - [title, setTitle] = useState('Stream Radio'), - [images, setImages] = useState([]), - [currentListeners, setCurrentListeners] = useState(0), - [maxListeners, setMaxListeners] = useState(0), - [paused, setPaused] = useState(true), - audioElmRef = useRef(null), - loadedAnalyzer = useRef(false), - once = useRef(false), - audioAnalyzer = () => { - const audioCtx = new (window.AudioContext || window.webkitAudioContext)(), - source = audioCtx.createMediaElementSource(audioElmRef.current), - analyzer = audioCtx.createAnalyser() - analyzer.fftSize = 2048 - const bufferLength = analyzer.frequencyBinCount, - dataArray = new Uint8Array(bufferLength) - source.connect(analyzer) - source.connect(audioCtx.destination) - source.onended = () => { - source.disconnect() - } - setAnalyzerData({ analyzer, bufferLength, dataArray }) - }, - loadData = async () => { - try { - const response = await fetch('/stream.json'), - json = await response.json() - setJson(json) - } catch (err) { - console.error('Error fetching data: ' + err.message) - } - }, - loadListeners = async () => { - try { - const response = await fetch('/status.xsl'), - data = await response.text(), - parser = new DOMParser(), - xmlDoc = parser.parseFromString(data, 'text/html'), - listeners = xmlDoc.getElementsByTagName('td') - for (let i = 0; i < listeners.length; i++) { - if (i === 9) { - setCurrentListeners(listeners[i].textContent) - } else if (i === 11) { - setMaxListeners(listeners[i].textContent) - } - } - } catch (err) { - console.error('Error fetching data: ' + err.message) - } - }, - loadImages = useCallback(async () => { - const response = await fetch(text), - data = await response.text() - setImages(data.split('\n').filter(line => line.length > 0)) - }), - load = useCallback(async () => { - await loadData() - await loadListeners() - }), - play = useCallback(async () => { - if (!loadedAnalyzer.current) { - audioAnalyzer() - loadedAnalyzer.current = true - } - setPaused(false) - setMuted(false) - await audioElmRef.current?.play() - }) + // Custom hook for managing background images + const { loadImages } = useBackgroundImages(text); + + // Custom hook for streaming data + const { + json, + currentListeners, + maxListeners, + title, + link, + trackInfo, + loadAllData + } = useStreamData(); + + // Custom hook for audio player functionality + const { + audioElmRef, + analyzerData, + muted, + paused, + play, + pause, + toggleMute + } = useAudioPlayer("/stream.mp3"); + + // Initialization flag to prevent multiple initializations + const initialized = useRef(false); + + // Initialize app and setup periodic data refresh useEffect(() => { - if (json?.media?.track[0] && bounce.search(json?.media?.track[0].Title) === -1) { - setBounce('Now Playing: ') - setLink(json?.media['@ref']?.replace('/musica', 'https://manalejandro.com')) - Object.keys(json.media.track[0]).map((key) => { - if (key === 'Title' || key === 'Performer' || key === 'Album') { - if (key === 'Title') { - setTitle(json.media.track[0][key]) - } - setBounce(data => data + json.media.track[0][key] + ' - ') - } else if (key === 'Filesize') { - setBounce(data => data + Math.round(parseInt(json.media.track[0][key]) / 1024 / 1024) + 'Mb - ') - } else if (key === 'Bitrate') { - setBounce(data => data + Math.floor(parseInt(json.media.track[0][key]) / 1000) + 'kbps - ') - } else if (key === 'Duration') { - setBounce(data => data + Math.floor(parseInt(json.media.track[0][key])) + 's - ') - } else if (key === 'Recorded_Date') { - setBounce(data => data + json.media.track[0][key]) - } - }) - } else if (!json?.media && bounce.search('Now Playing: Title not available') === -1) { - setLink('') - setTitle('Stream Radio') - setBounce('Now Playing: Title not available') - } - }, [json]) - useEffect(() => { - document.body.style.backgroundImage = `url('/wallpapers/${images[Math.floor(Math.random() * images.length)]}')` - }, [images]) - useEffect(() => { - if (once.current) return - once.current = true - M.AutoInit() - load() - loadImages() - setAudioUrl('/stream.mp3') - document.querySelector('input[type="range"]').addEventListener('change', (e) => { - setCurrentVolume(e.target.value / 100) - }) - const inter = setInterval(() => { - load() - }, (Math.floor(Math.random() * 20) + 10) * 1000), - interback = setInterval(() => { - loadImages() - }, (Math.floor(Math.random() * 60) + 90) * 1000) + if (initialized.current) return; + initialized.current = true; + + // Initialize Materialize components + M.AutoInit(); + + // Load initial data + loadAllData(); + loadImages(); + + // Set up periodic data refresh + const dataRefreshInterval = setInterval(() => { + loadAllData(); + }, (Math.floor(Math.random() * 20) + 10) * 1000); + + // Set up periodic background image refresh + const imageRefreshInterval = setInterval(() => { + loadImages(); + }, (Math.floor(Math.random() * 60) + 90) * 1000); + + // Cleanup function return () => { - clearInterval(inter) - clearInterval(interback) - } - }, []) - useEffect(() => { - if (audioElmRef.current && audioElmRef.current.volume !== currentVolume) { - if (currentVolume > 0 && currentVolume <= 1) { - setMuted(false) - } else if (currentVolume === 0) { - setMuted(true) - } - audioElmRef.current.volume = currentVolume - } - }, [currentVolume]) + clearInterval(dataRefreshInterval); + clearInterval(imageRefreshInterval); + }; + }, [loadAllData, loadImages]); + return ( <> -

hearing Stream Radio

+
+ {analyzerData && } -

{bounce}

+ + +

-
- {paused ? - - : - - }  - {muted ? - - : - - }   -    - - people {currentListeners} / {maxListeners} - -
-