initial commit

Signed-off-by: ale <ale@manalejandro.com>
Este commit está contenido en:
ale
2025-05-28 01:34:20 +02:00
commit 7528d2d4b8
Se han modificado 92 ficheros con 22103 adiciones y 0 borrados

25
.gitignore vendido Archivo normal
Ver fichero

@@ -0,0 +1,25 @@
# See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
# dependencies
/node_modules
/.pnp
.pnp.js
# testing
/coverage
# production
/build
# misc
.DS_Store
.env.local
.env.development.local
.env.test.local
.env.production.local
npm-debug.log*
yarn-debug.log*
yarn-error.log*
*.lock
*-lock.json

3
README.md Archivo normal
Ver fichero

@@ -0,0 +1,3 @@
# 📻 Stream Radio
## A simple radio streaming react app

41
package.json Archivo normal
Ver fichero

@@ -0,0 +1,41 @@
{
"name": "stream-radio",
"version": "0.1.0",
"private": true,
"dependencies": {
"@testing-library/dom": "^10.4.0",
"@testing-library/jest-dom": "^6.6.3",
"@testing-library/react": "^16.3.0",
"@testing-library/user-event": "^13.5.0",
"materialize-css": "^1.0.0",
"react": "^19.1.0",
"react-dom": "^19.1.0",
"react-materialize": "^3.10.0",
"react-scripts": "5.0.1",
"web-vitals": "^2.1.4"
},
"scripts": {
"start": "react-scripts start",
"build": "react-scripts build",
"test": "react-scripts test",
"eject": "react-scripts eject"
},
"eslintConfig": {
"extends": [
"react-app",
"react-app/jest"
]
},
"browserslist": {
"production": [
">0.2%",
"not dead",
"not op_mini all"
],
"development": [
"last 1 chrome version",
"last 1 firefox version",
"last 1 safari version"
]
}
}

9085
public/css/materialize.css vendido Archivo normal

La diferencia del archivo ha sido suprimido porque es demasiado grande Cargar Diff

13
public/css/materialize.min.css vendido Archivo normal

Las diferiencias del archivo han sido suprimidas porque una o mas lineas son muy largas

BIN
public/favicon.ico Archivo normal

Archivo binario no mostrado.

Después

Anchura:  |  Altura:  |  Tamaño: 4.2 KiB

50
public/index.html Archivo normal
Ver fichero

@@ -0,0 +1,50 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8" />
<link rel="icon" href="%PUBLIC_URL%/favicon.ico" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<meta name="theme-color" content="#000000" />
<meta
name="description"
content="Stream Radio is a web application that allows you to listen to your favorite radio stations online."
/>
<meta
name="keywords"
content="Stream Radio, radio, online radio, music, streaming"
/>
<meta name="author" content="manalejandro.com" />
<link rel="apple-touch-icon" href="%PUBLIC_URL%/logo192.png" />
<!--
manifest.json provides metadata used when your web app is installed on a
user's mobile device or desktop. See https://developers.google.com/web/fundamentals/web-app-manifest/
-->
<link rel="manifest" href="%PUBLIC_URL%/manifest.json" />
<!--
Notice the use of %PUBLIC_URL% in the tags above.
It will be replaced with the URL of the `public` folder during the build.
Only files inside the `public` folder can be referenced from the HTML.
Unlike "/favicon.ico" or "favicon.ico", "%PUBLIC_URL%/favicon.ico" will
work correctly both with client-side routing and a non-root public URL.
Learn how to configure a non-root public URL by running `npm run build`.
-->
<script src="js/materialize.min.js" defer></script>
<link rel="stylesheet" href="css/materialize.min.css" />
<title>Stream Radio</title>
</head>
<body>
<noscript>You need to enable JavaScript to run this app.</noscript>
<div id="root"></div>
<!--
This HTML file is a template.
If you open it directly in the browser, you will see an empty page.
You can add webfonts, meta tags, or analytics to this file.
The build step will place the bundled scripts into the <body> tag.
To begin the development, run `npm start` or `yarn start`.
To create a production bundle, use `npm run build` or `yarn build`.
-->
</body>
</html>

12374
public/js/materialize.js vendido Archivo normal

La diferencia del archivo ha sido suprimido porque es demasiado grande Cargar Diff

6
public/js/materialize.min.js vendido Archivo normal

Las diferiencias del archivo han sido suprimidas porque una o mas lineas son muy largas

BIN
public/logo192.png Archivo normal

Archivo binario no mostrado.

Después

Anchura:  |  Altura:  |  Tamaño: 7.4 KiB

BIN
public/logo512.png Archivo normal

Archivo binario no mostrado.

Después

Anchura:  |  Altura:  |  Tamaño: 12 KiB

26
public/manifest.json Archivo normal
Ver fichero

@@ -0,0 +1,26 @@
{
"short_name": "Stream Radio",
"name": "Stream Radio",
"description": "A simple radio streaming app",
"icons": [
{
"src": "favicon.ico",
"sizes": "32x32",
"type": "image/x-icon"
},
{
"src": "logo192.png",
"type": "image/png",
"sizes": "192x192"
},
{
"src": "logo512.png",
"type": "image/png",
"sizes": "512x512"
}
],
"start_url": ".",
"display": "standalone",
"theme_color": "#000000",
"background_color": "#ffffff"
}

3
public/robots.txt Archivo normal
Ver fichero

@@ -0,0 +1,3 @@
# https://www.robotstxt.org/robotstxt.html
User-agent: *
Disallow:

67
public/wallpapers/list.txt Archivo normal
Ver fichero

@@ -0,0 +1,67 @@
pexels-baskincreativeco-1480807.jpg
pexels-belle-co-99483-847393.jpg
pexels-bess-hamiti-83687-36487.jpg
pexels-carlos-oliva-1966452-3586966.jpg
pexels-christian-heitz-285904-842711.jpg
pexels-dreamypixel-547115.jpg
pexels-eberhardgross-1062249.jpg
pexels-eberhardgross-1287075.jpg
pexels-eberhardgross-1287089.jpg
pexels-eberhardgross-1301976.jpg
pexels-eberhardgross-1367192(1).jpg
pexels-eberhardgross-1367192.jpg
pexels-eberhardgross-1612351.jpg
pexels-eberhardgross-1612360.jpg
pexels-eberhardgross-1612362.jpg
pexels-eberhardgross-1612371.jpg
pexels-eberhardgross-1624255.jpg
pexels-eberhardgross-534164.jpg
pexels-eberhardgross-629167.jpg
pexels-eberhardgross-691668.jpg
pexels-eberhardgross-707344.jpg
pexels-eberhardgross-730981.jpg
pexels-esan-2085998.jpg
pexels-fotios-photos-109260.jpg
pexels-francesco-ungaro-1525041.jpg
pexels-gochrisgoxyz-1477166.jpg
pexels-johnnoibn-1448136.jpg
pexels-joshkjack-135018.jpg
pexels-jplenio-1110656.jpg
pexels-jplenio-1146708.jpg
pexels-jplenio-1435075.jpg
pexels-kasperphotography-1042423.jpg
pexels-katja-79053-592077.jpg
pexels-lastly-937782.jpg
pexels-lilartsy-1213447.jpg
pexels-maxfrancis-2246476.jpg
pexels-mdsnmdsnmdsn-1831234.jpg
pexels-mdx014-814499.jpg
pexels-michal-pech-213601-1632044.jpg
pexels-no-name-14543-66997.jpg
pexels-pixabay-158063.jpg
pexels-pixabay-33109(1).jpg
pexels-pixabay-33109.jpg
pexels-pixabay-33545.jpg
pexels-pixabay-358532.jpg
pexels-pixabay-41004.jpg
pexels-pixabay-414144.jpg
pexels-pixabay-416160.jpg
pexels-pixabay-459203.jpg
pexels-pixabay-462162.jpg
pexels-pixabay-50594.jpg
pexels-pixabay-50686.jpg
pexels-pixabay-52500.jpg
pexels-rpnickson-2559941.jpg
pexels-rpnickson-2647990.jpg
pexels-samandgos-709552.jpg
pexels-samkolder-2387873.jpg
pexels-sebastian-312105.jpg
pexels-simon73-1183099.jpg
pexels-souvenirpixels-1519088.jpg
pexels-souvenirpixels-417074.jpg
pexels-stefanstefancik-919606.jpg
pexels-stywo-1054289.jpg
pexels-stywo-1668246.jpg
pexels-therato-1933239.jpg
pexels-todd-trapani-488382-1198817.jpg
pexels-umaraffan499-21787.jpg

Archivo binario no mostrado.

Después

Anchura:  |  Altura:  |  Tamaño: 5.0 MiB

Archivo binario no mostrado.

Después

Anchura:  |  Altura:  |  Tamaño: 2.0 MiB

Archivo binario no mostrado.

Después

Anchura:  |  Altura:  |  Tamaño: 109 KiB

Archivo binario no mostrado.

Después

Anchura:  |  Altura:  |  Tamaño: 991 KiB

Archivo binario no mostrado.

Después

Anchura:  |  Altura:  |  Tamaño: 1.8 MiB

Archivo binario no mostrado.

Después

Anchura:  |  Altura:  |  Tamaño: 1.3 MiB

Archivo binario no mostrado.

Después

Anchura:  |  Altura:  |  Tamaño: 3.9 MiB

Archivo binario no mostrado.

Después

Anchura:  |  Altura:  |  Tamaño: 478 KiB

Archivo binario no mostrado.

Después

Anchura:  |  Altura:  |  Tamaño: 1.4 MiB

Archivo binario no mostrado.

Después

Anchura:  |  Altura:  |  Tamaño: 540 KiB

Archivo binario no mostrado.

Después

Anchura:  |  Altura:  |  Tamaño: 746 KiB

Archivo binario no mostrado.

Después

Anchura:  |  Altura:  |  Tamaño: 746 KiB

Archivo binario no mostrado.

Después

Anchura:  |  Altura:  |  Tamaño: 1.6 MiB

Archivo binario no mostrado.

Después

Anchura:  |  Altura:  |  Tamaño: 912 KiB

Archivo binario no mostrado.

Después

Anchura:  |  Altura:  |  Tamaño: 749 KiB

Archivo binario no mostrado.

Después

Anchura:  |  Altura:  |  Tamaño: 1.2 MiB

Archivo binario no mostrado.

Después

Anchura:  |  Altura:  |  Tamaño: 929 KiB

Archivo binario no mostrado.

Después

Anchura:  |  Altura:  |  Tamaño: 3.8 MiB

Archivo binario no mostrado.

Después

Anchura:  |  Altura:  |  Tamaño: 883 KiB

Archivo binario no mostrado.

Después

Anchura:  |  Altura:  |  Tamaño: 1.8 MiB

Archivo binario no mostrado.

Después

Anchura:  |  Altura:  |  Tamaño: 762 KiB

Archivo binario no mostrado.

Después

Anchura:  |  Altura:  |  Tamaño: 2.2 MiB

Archivo binario no mostrado.

Después

Anchura:  |  Altura:  |  Tamaño: 3.5 MiB

Archivo binario no mostrado.

Después

Anchura:  |  Altura:  |  Tamaño: 2.3 MiB

Archivo binario no mostrado.

Después

Anchura:  |  Altura:  |  Tamaño: 3.1 MiB

Archivo binario no mostrado.

Después

Anchura:  |  Altura:  |  Tamaño: 665 KiB

Archivo binario no mostrado.

Después

Anchura:  |  Altura:  |  Tamaño: 3.0 MiB

Archivo binario no mostrado.

Después

Anchura:  |  Altura:  |  Tamaño: 1.8 MiB

Archivo binario no mostrado.

Después

Anchura:  |  Altura:  |  Tamaño: 3.2 MiB

Archivo binario no mostrado.

Después

Anchura:  |  Altura:  |  Tamaño: 1.4 MiB

Archivo binario no mostrado.

Después

Anchura:  |  Altura:  |  Tamaño: 562 KiB

Archivo binario no mostrado.

Después

Anchura:  |  Altura:  |  Tamaño: 899 KiB

Archivo binario no mostrado.

Después

Anchura:  |  Altura:  |  Tamaño: 1.0 MiB

Archivo binario no mostrado.

Después

Anchura:  |  Altura:  |  Tamaño: 573 KiB

Archivo binario no mostrado.

Después

Anchura:  |  Altura:  |  Tamaño: 1.6 MiB

Archivo binario no mostrado.

Después

Anchura:  |  Altura:  |  Tamaño: 2.4 MiB

Archivo binario no mostrado.

Después

Anchura:  |  Altura:  |  Tamaño: 100 KiB

Archivo binario no mostrado.

Después

Anchura:  |  Altura:  |  Tamaño: 3.5 MiB

Archivo binario no mostrado.

Después

Anchura:  |  Altura:  |  Tamaño: 3.4 MiB

Archivo binario no mostrado.

Después

Anchura:  |  Altura:  |  Tamaño: 849 KiB

Archivo binario no mostrado.

Después

Anchura:  |  Altura:  |  Tamaño: 636 KiB

Archivo binario no mostrado.

Después

Anchura:  |  Altura:  |  Tamaño: 2.3 MiB

Archivo binario no mostrado.

Después

Anchura:  |  Altura:  |  Tamaño: 2.3 MiB

Archivo binario no mostrado.

Después

Anchura:  |  Altura:  |  Tamaño: 652 KiB

Archivo binario no mostrado.

Después

Anchura:  |  Altura:  |  Tamaño: 2.5 MiB

Archivo binario no mostrado.

Después

Anchura:  |  Altura:  |  Tamaño: 1.3 MiB

Archivo binario no mostrado.

Después

Anchura:  |  Altura:  |  Tamaño: 411 KiB

Archivo binario no mostrado.

Después

Anchura:  |  Altura:  |  Tamaño: 1.4 MiB

Archivo binario no mostrado.

Después

Anchura:  |  Altura:  |  Tamaño: 3.5 MiB

Archivo binario no mostrado.

Después

Anchura:  |  Altura:  |  Tamaño: 376 KiB

Archivo binario no mostrado.

Después

Anchura:  |  Altura:  |  Tamaño: 1.4 MiB

Archivo binario no mostrado.

Después

Anchura:  |  Altura:  |  Tamaño: 227 KiB

Archivo binario no mostrado.

Después

Anchura:  |  Altura:  |  Tamaño: 467 KiB

Archivo binario no mostrado.

Después

Anchura:  |  Altura:  |  Tamaño: 1.6 MiB

Archivo binario no mostrado.

Después

Anchura:  |  Altura:  |  Tamaño: 976 KiB

Archivo binario no mostrado.

Después

Anchura:  |  Altura:  |  Tamaño: 1.2 MiB

Archivo binario no mostrado.

Después

Anchura:  |  Altura:  |  Tamaño: 2.6 MiB

Archivo binario no mostrado.

Después

Anchura:  |  Altura:  |  Tamaño: 3.3 MiB

Archivo binario no mostrado.

Después

Anchura:  |  Altura:  |  Tamaño: 3.2 MiB

Archivo binario no mostrado.

Después

Anchura:  |  Altura:  |  Tamaño: 475 KiB

Archivo binario no mostrado.

Después

Anchura:  |  Altura:  |  Tamaño: 1.2 MiB

Archivo binario no mostrado.

Después

Anchura:  |  Altura:  |  Tamaño: 2.9 MiB

Archivo binario no mostrado.

Después

Anchura:  |  Altura:  |  Tamaño: 1.5 MiB

Archivo binario no mostrado.

Después

Anchura:  |  Altura:  |  Tamaño: 1.1 MiB

Archivo binario no mostrado.

Después

Anchura:  |  Altura:  |  Tamaño: 1.1 MiB

Archivo binario no mostrado.

Después

Anchura:  |  Altura:  |  Tamaño: 4.9 MiB

Archivo binario no mostrado.

Después

Anchura:  |  Altura:  |  Tamaño: 686 KiB

101
src/App.css Archivo normal
Ver fichero

@@ -0,0 +1,101 @@
body {
text-align: center;
color: white;
background-position: center center;
background-repeat: no-repeat;
background-attachment: fixed;
background-size: cover;
}
h1 {
padding-top: 16vh;
font-size: 4rem;
}
h4 {
margin: 0 auto;
overflow: hidden;
background-color: rgba(255, 255, 255, 0.3);
transition: 0.3s;
width: 85%;
font-size: x-large;
}
h4:hover {
width: 95%;
}
.bounce a {
border-bottom: 1px solid white;
}
.bounce {
animation: marquee 10s linear infinite;
}
.bounce:hover {
animation-play-state: paused;
}
a,
a:visited,
a:active,
a:hover {
color: white;
text-shadow: 0 0 5px #000;
text-decoration: none;
cursor: pointer;
}
@keyframes marquee {
0% {
transform: translateX(100vw);
}
100% {
transform: translateX(-100%);
}
}
audio {
display: none;
}
.player {
width: 26rem;
margin: 0 auto;
padding: 1rem;
border-radius: 10px;
}
input[type="range"],
input[type="range"]::-moz-range-thumb,
input[type="range"]::-webkit-slider-runnable-track,
input[type="range"]::-webkit-slider-thumb {
-webkit-appearance: none;
appearance: none;
cursor: pointer;
border: none;
margin: 0;
}
input[type="range"]::-moz-range-thumb,
input[type="range"]::-webkit-slider-thumb,
input[type=range]+.thumb,
.thumb {
background-color: #795548 !important;
}
input[type="range"],
input[type="range"]::-webkit-slider-runnable-track {
background-color: #4e342e !important;
}
.range-field {
vertical-align: middle;;
margin: 0 auto;
display: inline-block;
width: 6rem;
border: 0;
color: white;
}

167
src/App.js Archivo normal
Ver fichero

@@ -0,0 +1,167 @@
import "./App.css";
import { useCallback, useEffect, useRef, useState } from "react";
import WaveForm from "./WaveForm";
import { Button, Icon, Range } from "react-materialize";
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),
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 = () => {
fetch('/stream.json').then(response => response.json()).then(json => {
setJson(json)
}).catch(err => {
console.error('Error fetching data: ' + err.message)
})
},
loadImages = () => {
fetch('/wallpapers/list.txt').then(response => response.text()).then(text => {
setImages(text.split('\n').filter(line => line.length > 0))
}).catch(err => {
console.error('Error fetching data: ' + err.message)
})
},
loadListeners = () => {
fetch('/status.xsl').then(response => response.text()).then(data => {
const 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)
})
},
play = useCallback(async () => {
if (!once.current) {
audioAnalyzer()
once.current = true
}
setPaused(false)
setMuted(false)
await audioElmRef.current?.play()
})
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: null') === -1) {
setLink('')
setTitle('Stream Radio')
setBounce('Now Playing: null')
}
}, [json])
useEffect(() => {
document.body.style.backgroundImage = `url('/wallpapers/${images[Math.floor(Math.random() * images.length)]}')`
}, [images])
useEffect(() => {
loadData()
loadImages()
loadListeners()
setAudioUrl('/stream.mp3')
document.querySelector('input[type="range"]').addEventListener('change', (e) => {
setCurrentVolume(e.target.value / 100)
})
const inter = setInterval(() => {
loadData()
loadListeners()
}, (Math.floor(Math.random() * 20) + 10) * 1000),
interback = setInterval(() => {
loadImages()
}, (Math.floor(Math.random() * 60) + 90) * 1000)
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])
return (
<>
<h1><a href="https://git.manalejandro.com/ale/stream-radio" title="Stream Radio Git Repository" alt="Stream Radio Git Repository" target="_blank"><Icon className="medium">hearing</Icon> Stream Radio</a></h1>
{analyzerData && <WaveForm analyzerData={analyzerData} />}
<h4><p className="bounce"><a href={link} target="_blank" title={title} alt={title}>{bounce}</a></p></h4>
<br /><br />
<div className="brown darken-3 player">
{paused ?
<Button node="button" className="brown" onClick={() => play()} waves="light" floating><Icon>play_arrow</Icon></Button>
:
<Button node="button" className="brown" onClick={() => {
audioElmRef.current?.pause()
setPaused(true)
}} waves="light" floating><Icon>pause</Icon></Button>
}&nbsp;
{muted ?
<Button node="button" className="brown" onClick={() => setMuted(false)} waves="light" floating><Icon>volume_off</Icon></Button>
:
<Button node="button" className="brown" onClick={() => setMuted(true)} waves="light" floating><Icon>volume_up</Icon></Button>
}&nbsp;&nbsp;
<Range className="brown" waves="light"
min={0}
max={100}
step={1}
/>&nbsp;&nbsp;
<span>
<Icon tiny>people</Icon>&nbsp;{currentListeners} / {maxListeners}
</span>
</div >
<audio src={audioUrl} ref={audioElmRef} volume={Math.log(currentVolume)} preload={"none"} muted={muted} controls={false} />
</>
)
}
export default App

8
src/App.test.js Archivo normal
Ver fichero

@@ -0,0 +1,8 @@
import { render, screen } from '@testing-library/react';
import App from './App';
test('renders learn react link', () => {
render(<App />);
const linkElement = screen.getByText(/learn react/i);
expect(linkElement).toBeInTheDocument();
});

55
src/WaveForm.jsx Archivo normal
Ver fichero

@@ -0,0 +1,55 @@
import { useRef, useEffect } from "react";
import useSize from "./useSize";
const animateBars = (analyser, canvas, canvasCtx, dataArray, bufferLength) => {
analyser.getByteFrequencyData(dataArray)
canvasCtx.fillStyle = "#000"
const HEIGHT = canvas.height / 2
var barWidth = Math.ceil(canvas.width / bufferLength) * 2.5
let barHeight
let x = 0
for (var i = 0; i < bufferLength; i++) {
barHeight = (dataArray[i] / 255) * HEIGHT
const blueShade = Math.floor((dataArray[i] / 255) * 4) // generate a shade of blue based on the audio input
const blueHex = ['rgba(255,255,255,0.5)', 'rgba(255,255,255,0.4)', 'rgba(255,255,255,0.3)', 'rgba(255,255,255,0.2)'][blueShade]; // use react logo blue shades
canvasCtx.fillStyle = blueHex
canvasCtx.fillRect(x, HEIGHT - barHeight, barWidth, barHeight)
x += barWidth + 1
}
},
WaveForm = ({ analyzerData }) => {
const canvasRef = useRef(null),
{ dataArray, analyzer, bufferLength } = analyzerData,
[width, height] = useSize(),
draw = (dataArray, analyzer, bufferLength) => {
const canvas = canvasRef.current
if (!canvas || !analyzer) return
const canvasCtx = canvas.getContext("2d"),
animate = () => {
requestAnimationFrame(animate)
// eslint-disable-next-line no-self-assign
canvas.width = canvas.width
canvasCtx.translate(0, canvas.offsetHeight / 2)
animateBars(analyzer, canvas, canvasCtx, dataArray, bufferLength)
}
animate()
}
useEffect(() => {
draw(dataArray, analyzer, bufferLength)
}, [dataArray, analyzer, bufferLength])
return (
<canvas
style={{
position: "absolute",
top: "0",
left: "0",
zIndex: "-10"
}}
ref={canvasRef}
width={width}
height={height}
/>
)
}
export default WaveForm

26
src/index.css Archivo normal
Ver fichero

@@ -0,0 +1,26 @@
@font-face {
font-family: 'Material Icons';
font-style: normal;
font-weight: 400;
src: url(./material-icons.ttf) format('truetype');
}
.material-icons {
font-family: 'Material Icons';
font-weight: normal;
font-style: normal;
font-size: 24px;
line-height: 1;
letter-spacing: normal;
text-transform: none;
display: inline-block;
white-space: nowrap;
word-wrap: normal;
direction: ltr;
}
body {
font-family: monospace, monospace;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
}

17
src/index.js Archivo normal
Ver fichero

@@ -0,0 +1,17 @@
import React from 'react';
import ReactDOM from 'react-dom/client';
import './index.css';
import App from './App';
import reportWebVitals from './reportWebVitals';
const root = ReactDOM.createRoot(document.getElementById('root'));
root.render(
<React.StrictMode>
<App />
</React.StrictMode>
);
// If you want to start measuring performance in your app, pass a function
// to log results (for example: reportWebVitals(console.log))
// or send to an analytics endpoint. Learn more: https://bit.ly/CRA-vitals
reportWebVitals();

1
src/logo.svg Archivo normal
Ver fichero

@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 841.9 595.3"><g fill="#61DAFB"><path d="M666.3 296.5c0-32.5-40.7-63.3-103.1-82.4 14.4-63.6 8-114.2-20.2-130.4-6.5-3.8-14.1-5.6-22.4-5.6v22.3c4.6 0 8.3.9 11.4 2.6 13.6 7.8 19.5 37.5 14.9 75.7-1.1 9.4-2.9 19.3-5.1 29.4-19.6-4.8-41-8.5-63.5-10.9-13.5-18.5-27.5-35.3-41.6-50 32.6-30.3 63.2-46.9 84-46.9V78c-27.5 0-63.5 19.6-99.9 53.6-36.4-33.8-72.4-53.2-99.9-53.2v22.3c20.7 0 51.4 16.5 84 46.6-14 14.7-28 31.4-41.3 49.9-22.6 2.4-44 6.1-63.6 11-2.3-10-4-19.7-5.2-29-4.7-38.2 1.1-67.9 14.6-75.8 3-1.8 6.9-2.6 11.5-2.6V78.5c-8.4 0-16 1.8-22.6 5.6-28.1 16.2-34.4 66.7-19.9 130.1-62.2 19.2-102.7 49.9-102.7 82.3 0 32.5 40.7 63.3 103.1 82.4-14.4 63.6-8 114.2 20.2 130.4 6.5 3.8 14.1 5.6 22.5 5.6 27.5 0 63.5-19.6 99.9-53.6 36.4 33.8 72.4 53.2 99.9 53.2 8.4 0 16-1.8 22.6-5.6 28.1-16.2 34.4-66.7 19.9-130.1 62-19.1 102.5-49.9 102.5-82.3zm-130.2-66.7c-3.7 12.9-8.3 26.2-13.5 39.5-4.1-8-8.4-16-13.1-24-4.6-8-9.5-15.8-14.4-23.4 14.2 2.1 27.9 4.7 41 7.9zm-45.8 106.5c-7.8 13.5-15.8 26.3-24.1 38.2-14.9 1.3-30 2-45.2 2-15.1 0-30.2-.7-45-1.9-8.3-11.9-16.4-24.6-24.2-38-7.6-13.1-14.5-26.4-20.8-39.8 6.2-13.4 13.2-26.8 20.7-39.9 7.8-13.5 15.8-26.3 24.1-38.2 14.9-1.3 30-2 45.2-2 15.1 0 30.2.7 45 1.9 8.3 11.9 16.4 24.6 24.2 38 7.6 13.1 14.5 26.4 20.8 39.8-6.3 13.4-13.2 26.8-20.7 39.9zm32.3-13c5.4 13.4 10 26.8 13.8 39.8-13.1 3.2-26.9 5.9-41.2 8 4.9-7.7 9.8-15.6 14.4-23.7 4.6-8 8.9-16.1 13-24.1zM421.2 430c-9.3-9.6-18.6-20.3-27.8-32 9 .4 18.2.7 27.5.7 9.4 0 18.7-.2 27.8-.7-9 11.7-18.3 22.4-27.5 32zm-74.4-58.9c-14.2-2.1-27.9-4.7-41-7.9 3.7-12.9 8.3-26.2 13.5-39.5 4.1 8 8.4 16 13.1 24 4.7 8 9.5 15.8 14.4 23.4zM420.7 163c9.3 9.6 18.6 20.3 27.8 32-9-.4-18.2-.7-27.5-.7-9.4 0-18.7.2-27.8.7 9-11.7 18.3-22.4 27.5-32zm-74 58.9c-4.9 7.7-9.8 15.6-14.4 23.7-4.6 8-8.9 16-13 24-5.4-13.4-10-26.8-13.8-39.8 13.1-3.1 26.9-5.8 41.2-7.9zm-90.5 125.2c-35.4-15.1-58.3-34.9-58.3-50.6 0-15.7 22.9-35.6 58.3-50.6 8.6-3.7 18-7 27.7-10.1 5.7 19.6 13.2 40 22.5 60.9-9.2 20.8-16.6 41.1-22.2 60.6-9.9-3.1-19.3-6.5-28-10.2zM310 490c-13.6-7.8-19.5-37.5-14.9-75.7 1.1-9.4 2.9-19.3 5.1-29.4 19.6 4.8 41 8.5 63.5 10.9 13.5 18.5 27.5 35.3 41.6 50-32.6 30.3-63.2 46.9-84 46.9-4.5-.1-8.3-1-11.3-2.7zm237.2-76.2c4.7 38.2-1.1 67.9-14.6 75.8-3 1.8-6.9 2.6-11.5 2.6-20.7 0-51.4-16.5-84-46.6 14-14.7 28-31.4 41.3-49.9 22.6-2.4 44-6.1 63.6-11 2.3 10.1 4.1 19.8 5.2 29.1zm38.5-66.7c-8.6 3.7-18 7-27.7 10.1-5.7-19.6-13.2-40-22.5-60.9 9.2-20.8 16.6-41.1 22.2-60.6 9.9 3.1 19.3 6.5 28.1 10.2 35.4 15.1 58.3 34.9 58.3 50.6-.1 15.7-23 35.6-58.4 50.6zM320.8 78.4z"/><circle cx="420.9" cy="296.5" r="45.7"/><path d="M520.5 78.1z"/></g></svg>

Después

Anchura:  |  Altura:  |  Tamaño: 2.6 KiB

BIN
src/material-icons.ttf Archivo normal

Archivo binario no mostrado.

13
src/reportWebVitals.js Archivo normal
Ver fichero

@@ -0,0 +1,13 @@
const reportWebVitals = onPerfEntry => {
if (onPerfEntry && onPerfEntry instanceof Function) {
import('web-vitals').then(({ getCLS, getFID, getFCP, getLCP, getTTFB }) => {
getCLS(onPerfEntry);
getFID(onPerfEntry);
getFCP(onPerfEntry);
getLCP(onPerfEntry);
getTTFB(onPerfEntry);
});
}
};
export default reportWebVitals;

5
src/setupTests.js Archivo normal
Ver fichero

@@ -0,0 +1,5 @@
// jest-dom adds custom jest matchers for asserting on DOM nodes.
// allows you to do things like:
// expect(element).toHaveTextContent(/react/i)
// learn more: https://github.com/testing-library/jest-dom
import '@testing-library/jest-dom';

17
src/useSize.js Archivo normal
Ver fichero

@@ -0,0 +1,17 @@
import { useCallback, useEffect, useState } from "react";
const useSize = () => {
const [width, setWidth] = useState(0),
[height, setHeight] = useState(0),
setSizes = useCallback(() => {
setWidth(window.innerWidth)
setHeight(window.innerHeight)
}, [setWidth, setHeight])
useEffect(() => {
window.addEventListener("resize", setSizes)
setSizes()
}, [setSizes])
return [width, height]
}
export default useSize