refactor AI WaveForm.jsx
Signed-off-by: ale <ale@manalejandro.com>
This commit is contained in:
parent
8bbd0c3b27
commit
1cb4b8a023
@ -29,7 +29,6 @@
|
|||||||
work correctly both with client-side routing and a non-root public URL.
|
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`.
|
Learn how to configure a non-root public URL by running `npm run build`.
|
||||||
-->
|
-->
|
||||||
<script src="js/materialize.min.js" defer></script>
|
|
||||||
<title>Stream Radio</title>
|
<title>Stream Radio</title>
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
|
6
public/js/materialize.min.js
vendored
6
public/js/materialize.min.js
vendored
File diff suppressed because one or more lines are too long
60
src/App.js
60
src/App.js
@ -1,7 +1,9 @@
|
|||||||
import "./App.css";
|
import "./App.css";
|
||||||
import { useCallback, useEffect, useRef, useState } from "react";
|
import { useCallback, useEffect, useRef, useState } from "react";
|
||||||
import WaveForm from "./WaveForm";
|
import WaveForm from "./WaveForm";
|
||||||
|
import M from "materialize-css";
|
||||||
import { Button, Icon, Range } from "react-materialize";
|
import { Button, Icon, Range } from "react-materialize";
|
||||||
|
import text from "./list.txt";
|
||||||
|
|
||||||
const App = () => {
|
const App = () => {
|
||||||
const [audioUrl, setAudioUrl] = useState(null),
|
const [audioUrl, setAudioUrl] = useState(null),
|
||||||
@ -17,6 +19,7 @@ const App = () => {
|
|||||||
[maxListeners, setMaxListeners] = useState(0),
|
[maxListeners, setMaxListeners] = useState(0),
|
||||||
[paused, setPaused] = useState(true),
|
[paused, setPaused] = useState(true),
|
||||||
audioElmRef = useRef(null),
|
audioElmRef = useRef(null),
|
||||||
|
loadedAnalyzer = useRef(false),
|
||||||
once = useRef(false),
|
once = useRef(false),
|
||||||
audioAnalyzer = () => {
|
audioAnalyzer = () => {
|
||||||
const audioCtx = new (window.AudioContext || window.webkitAudioContext)(),
|
const audioCtx = new (window.AudioContext || window.webkitAudioContext)(),
|
||||||
@ -32,23 +35,20 @@ const App = () => {
|
|||||||
}
|
}
|
||||||
setAnalyzerData({ analyzer, bufferLength, dataArray })
|
setAnalyzerData({ analyzer, bufferLength, dataArray })
|
||||||
},
|
},
|
||||||
loadData = () => {
|
loadData = async () => {
|
||||||
fetch('/stream.json').then(response => response.json()).then(json => {
|
try {
|
||||||
|
const response = await fetch('/stream.json'),
|
||||||
|
json = await response.json()
|
||||||
setJson(json)
|
setJson(json)
|
||||||
}).catch(err => {
|
} catch (err) {
|
||||||
console.error('Error fetching data: ' + err.message)
|
console.error('Error fetching data: ' + err.message)
|
||||||
})
|
}
|
||||||
},
|
},
|
||||||
loadImages = () => {
|
loadListeners = async () => {
|
||||||
fetch('/wallpapers/list.txt').then(response => response.text()).then(text => {
|
try {
|
||||||
setImages(text.split('\n').filter(line => line.length > 0))
|
const response = await fetch('/status.xsl'),
|
||||||
}).catch(err => {
|
data = await response.text(),
|
||||||
console.error('Error fetching data: ' + err.message)
|
parser = new DOMParser(),
|
||||||
})
|
|
||||||
},
|
|
||||||
loadListeners = () => {
|
|
||||||
fetch('/status.xsl').then(response => response.text()).then(data => {
|
|
||||||
const parser = new DOMParser(),
|
|
||||||
xmlDoc = parser.parseFromString(data, 'text/html'),
|
xmlDoc = parser.parseFromString(data, 'text/html'),
|
||||||
listeners = xmlDoc.getElementsByTagName('td')
|
listeners = xmlDoc.getElementsByTagName('td')
|
||||||
for (let i = 0; i < listeners.length; i++) {
|
for (let i = 0; i < listeners.length; i++) {
|
||||||
@ -58,14 +58,23 @@ const App = () => {
|
|||||||
setMaxListeners(listeners[i].textContent)
|
setMaxListeners(listeners[i].textContent)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}).catch(err => {
|
} catch (err) {
|
||||||
console.error('Error fetching data: ' + err.message)
|
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 () => {
|
play = useCallback(async () => {
|
||||||
if (!once.current) {
|
if (!loadedAnalyzer.current) {
|
||||||
audioAnalyzer()
|
audioAnalyzer()
|
||||||
once.current = true
|
loadedAnalyzer.current = true
|
||||||
}
|
}
|
||||||
setPaused(false)
|
setPaused(false)
|
||||||
setMuted(false)
|
setMuted(false)
|
||||||
@ -91,26 +100,27 @@ const App = () => {
|
|||||||
setBounce(data => data + json.media.track[0][key])
|
setBounce(data => data + json.media.track[0][key])
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
} else if (!json?.media && bounce.search('Now Playing: null') === -1) {
|
} else if (!json?.media && bounce.search('Now Playing: Title not available') === -1) {
|
||||||
setLink('')
|
setLink('')
|
||||||
setTitle('Stream Radio')
|
setTitle('Stream Radio')
|
||||||
setBounce('Now Playing: null')
|
setBounce('Now Playing: Title not available')
|
||||||
}
|
}
|
||||||
}, [json])
|
}, [json])
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
document.body.style.backgroundImage = `url('/wallpapers/${images[Math.floor(Math.random() * images.length)]}')`
|
document.body.style.backgroundImage = `url('/wallpapers/${images[Math.floor(Math.random() * images.length)]}')`
|
||||||
}, [images])
|
}, [images])
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
loadData()
|
if (once.current) return
|
||||||
|
once.current = true
|
||||||
|
M.AutoInit()
|
||||||
|
load()
|
||||||
loadImages()
|
loadImages()
|
||||||
loadListeners()
|
|
||||||
setAudioUrl('/stream.mp3')
|
setAudioUrl('/stream.mp3')
|
||||||
document.querySelector('input[type="range"]').addEventListener('change', (e) => {
|
document.querySelector('input[type="range"]').addEventListener('change', (e) => {
|
||||||
setCurrentVolume(e.target.value / 100)
|
setCurrentVolume(e.target.value / 100)
|
||||||
})
|
})
|
||||||
const inter = setInterval(() => {
|
const inter = setInterval(() => {
|
||||||
loadData()
|
load()
|
||||||
loadListeners()
|
|
||||||
}, (Math.floor(Math.random() * 20) + 10) * 1000),
|
}, (Math.floor(Math.random() * 20) + 10) * 1000),
|
||||||
interback = setInterval(() => {
|
interback = setInterval(() => {
|
||||||
loadImages()
|
loadImages()
|
||||||
@ -159,7 +169,7 @@ const App = () => {
|
|||||||
<Icon tiny>people</Icon> {currentListeners} / {maxListeners}
|
<Icon tiny>people</Icon> {currentListeners} / {maxListeners}
|
||||||
</span>
|
</span>
|
||||||
</div >
|
</div >
|
||||||
<audio src={audioUrl} ref={audioElmRef} volume={Math.log(currentVolume)} preload={"none"} muted={muted} controls={false} />
|
<audio src={audioUrl} ref={audioElmRef} volume={Math.log10(currentVolume * 10)} preload={"none"} muted={muted} controls={false} />
|
||||||
</>
|
</>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
@ -1,55 +1,68 @@
|
|||||||
import { useRef, useEffect } from "react";
|
import { useRef, useEffect } from "react";
|
||||||
import useSize from "./useSize";
|
import useSize from "./useSize";
|
||||||
|
|
||||||
const animateBars = (analyser, canvas, canvasCtx, dataArray, bufferLength) => {
|
const BLUE_SHADES = [
|
||||||
analyser.getByteFrequencyData(dataArray)
|
"rgba(255,255,255,0.5)",
|
||||||
canvasCtx.fillStyle = "#000"
|
"rgba(255,255,255,0.4)",
|
||||||
const HEIGHT = canvas.height / 2
|
"rgba(255,255,255,0.3)",
|
||||||
var barWidth = Math.ceil(canvas.width / bufferLength) * 2.5
|
"rgba(255,255,255,0.2)",
|
||||||
let barHeight
|
];
|
||||||
let x = 0
|
|
||||||
for (var i = 0; i < bufferLength; i++) {
|
const animateBars = (analyser, canvas, ctx, dataArray, bufferLength) => {
|
||||||
barHeight = (dataArray[i] / 255) * HEIGHT
|
analyser.getByteFrequencyData(dataArray);
|
||||||
const blueShade = Math.floor((dataArray[i] / 255) * 4) // generate a shade of blue based on the audio input
|
const HEIGHT = canvas.height / 2;
|
||||||
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
|
const barWidth = Math.ceil(canvas.width / bufferLength) * 2.5;
|
||||||
canvasCtx.fillStyle = blueHex
|
let x = 0;
|
||||||
canvasCtx.fillRect(x, HEIGHT - barHeight, barWidth, barHeight)
|
for (let i = 0; i < bufferLength; i++) {
|
||||||
x += barWidth + 1
|
const barHeight = (dataArray[i] / 255) * HEIGHT;
|
||||||
}
|
const blueShade = Math.floor((dataArray[i] / 255) * 4);
|
||||||
},
|
ctx.fillStyle = BLUE_SHADES[blueShade];
|
||||||
WaveForm = ({ analyzerData }) => {
|
ctx.fillRect(x, HEIGHT - barHeight, barWidth, barHeight);
|
||||||
const canvasRef = useRef(null),
|
x += barWidth + 1;
|
||||||
{ 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()
|
|
||||||
}
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const WaveForm = ({ analyzerData }) => {
|
||||||
|
const canvasRef = useRef(null);
|
||||||
|
const { dataArray, analyzer, bufferLength } = analyzerData;
|
||||||
|
const [width, height] = useSize();
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
draw(dataArray, analyzer, bufferLength)
|
const canvas = canvasRef.current;
|
||||||
}, [dataArray, analyzer, bufferLength])
|
if (!canvas || !analyzer) return;
|
||||||
|
const ctx = canvas.getContext("2d");
|
||||||
|
|
||||||
|
let animationId;
|
||||||
|
|
||||||
|
const render = () => {
|
||||||
|
ctx.clearRect(0, 0, canvas.width, canvas.height);
|
||||||
|
ctx.save();
|
||||||
|
ctx.translate(0, canvas.height / 2);
|
||||||
|
animateBars(analyzer, canvas, ctx, dataArray, bufferLength);
|
||||||
|
ctx.restore();
|
||||||
|
animationId = requestAnimationFrame(render);
|
||||||
|
};
|
||||||
|
|
||||||
|
render();
|
||||||
|
|
||||||
|
return () => {
|
||||||
|
cancelAnimationFrame(animationId);
|
||||||
|
};
|
||||||
|
}, [dataArray, analyzer, bufferLength, width, height]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<canvas
|
<canvas
|
||||||
style={{
|
style={{
|
||||||
position: "absolute",
|
position: "absolute",
|
||||||
top: "0",
|
top: 0,
|
||||||
left: "0",
|
left: 0,
|
||||||
zIndex: "-10"
|
zIndex: 0,
|
||||||
}}
|
}}
|
||||||
ref={canvasRef}
|
ref={canvasRef}
|
||||||
width={width}
|
width={width}
|
||||||
height={height}
|
height={height}
|
||||||
/>
|
/>
|
||||||
)
|
);
|
||||||
}
|
};
|
||||||
|
|
||||||
export default WaveForm
|
export default WaveForm;
|
67
src/list.txt
Normal file
67
src/list.txt
Normal file
@ -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
|
Loading…
x
Reference in New Issue
Block a user