@robgold cool 😉
Czy wolisz polską wersję strony elektroda?
Nie, dziękuję Przekieruj mnie tamkacha36 wrote:Hi. This is probably my last attempt to program the ESP32-S3-N16R8. Something is definitely doing "wrong", I just don't know what. At the moment I have IDE v. 2.3.6, Teensy installed from within the IDE, some changes to the audio libraries and after a while of compiling the message - "Arduino\libraries\Audio_-_Adafruit_Fork/AudioStream.h:71:10: fatal error: kinetis.h: No such file or directory".
What do I still need to install so that I can finally compile the file correctly and upload it to the ESP?
After checking the installation instructions given to me by DJCheester, the same error with kinetis.h still occurs.
ejcon wrote:My suggestion pseudo-analyzer as style 5 and 6 For those who like gadgets.
MAJSTER XXL wrote:From me today such a new "settings menu" for version v3, to activate it I used another button of the remote control (in my case the PROG button). And how it works:
DJCheester wrote:I've had a bit of a look at the workings of this pseudo-analyser, and yes, to my eye the bars repeat every three jumps probably more in voltage (or digital value) than in a real analyser.
efi222 wrote:For the FFT, it will rather run out of calculating power.
DJCheester wrote:There is unlikely to be a difference. You have to extract fragments from the whole band anyway.Well, it doesn't look interesting, and would it be possible to fire off fewer bars, e.g. 5, 7, but it would go with the audio bands ?
DJCheester wrote:Well, it doesn't look interesting, and would fewer bars be able to fire up e.g. 5, 7, but it would go with the audio bands?
Greetings ...
<!DOCTYPE html>
<html lang="pl">
<head>
<meta charset="UTF-8">
<title>EVO – Radiobrowser + EQ16</title>
<meta name="viewport" content="width=device-width,initial-scale=1">
<style>
:root {
--bg: #111;
--card: #181818;
--fg: #eee;
--accent: #4caf50;
--accent-soft: #2e7d32;
--border: #333;
}
* { box-sizing: border-box; }
body {
margin: 0;
padding: 0;
font-family: system-ui, -apple-system, BlinkMacSystemFont,
"Segoe UI", sans-serif;
background: var(--bg);
color: var(--fg);
}
header {
padding: 10px 16px;
background: #222;
border-bottom: 1px solid var(--border);
display: flex;
justify-content: space-between;
align-items: center;
flex-wrap: wrap;
gap: 8px;
}
header h1 {
margin: 0;
font-size: 18px;
}
header .host {
font-size: 12px;
color: #aaa;
}
main {
min-height: calc(100vh - 46px);
display: flex;
justify-content: center;
align-items: flex-start;
padding: 18px 8px 40px;
}
.card {
width: 100%;
max-width: 960px;
background: var(--card);
border-radius: 12px;
border: 1px solid var(--border);
padding: 14px 14px 18px;
box-shadow: 0 0 12px rgba(0,0,0,0.6);
}
h2 {
margin: 0 0 6px;
font-size: 15px;
}
/* ANALIZATOR */
.analyzer-header {
margin-bottom: 4px;
}
.analyzer-desc {
font-size: 11px;
color: #aaa;
margin-bottom: 6px;
}
#spectrumCanvas {
width: 100%;
height: 160px; /* wy sze okno analizatora */
background: #000;
border-radius: 6px;
border: 1px solid #444;
display: block;
}
.an-freq-labels {
display: flex;
justify-content: space-between;
margin-top: 4px;
font-size: 10px;
color: #ccc;
}
.an-freq-label {
min-width: 0;
text-align: center;
flex: 1 1 auto;
}
/* Korektor */
.eq-header {
display: flex;
justify-content: space-between;
align-items: center;
gap: 8px;
margin-top: 18px;
}
.eq-toggle-group {
display: flex;
align-items: center;
gap: 8px;
}
.eq-toggle {
display: flex;
align-items: center;
gap: 4px;
font-size: 12px;
}
.eq-toggle input {
width: 16px;
height: 16px;
cursor: pointer;
}
.eq-info {
font-size: 11px;
color: #aaa;
margin-top: 4px;
margin-bottom: 8px;
}
.eq-sliders {
display: flex;
gap: 6px;
justify-content: space-between;
overflow-x: auto;
padding: 6px 2px 4px;
}
.eq-band {
display: flex;
flex-direction: column;
align-items: center;
font-size: 10px;
min-width: 24px;
}
.eq-slider-wrap {
height: 120px;
display: flex;
align-items: center;
justify-content: center;
}
.eq-slider {
-webkit-appearance: none;
appearance: none;
width: 120px;
height: 6px;
transform: rotate(-90deg);
background: #333;
border-radius: 4px;
outline: none;
}
.eq-slider::-webkit-slider-thumb {
-webkit-appearance: none;
appearance: none;
width: 10px;
height: 16px;
border-radius: 3px;
background: var(--accent);
cursor: pointer;
}
.eq-slider::-moz-range-thumb {
width: 10px;
height: 16px;
border-radius: 3px;
background: var(--accent);
cursor: pointer;
border: none;
}
.eq-band-label {
margin-top: 2px;
}
.eq-band-gain {
margin-top: 2px;
color: #ccc;
}
.eq-buttons {
margin-top: 8px;
display: flex;
flex-wrap: wrap;
gap: 6px;
font-size: 12px;
}
.eq-buttons button {
padding: 4px 8px;
border-radius: 6px;
border: 1px solid var(--border);
background: #333;
color: var(--fg);
cursor: pointer;
}
.eq-buttons button:hover {
background: #444;
}
/* Volume */
.vol-section {
margin-top: 14px;
font-size: 12px;
}
.vol-label-row {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 4px;
}
/* KRÓTSZY I WYŚRODKOWANY SUWAK GŁOŚNOŚCI */
.vol-slider {
-webkit-appearance: none;
appearance: none;
width: 40%; /* <-- tu ustaw długość, np. 40–60% */
height: 10px;
background: #4caf50;
border-radius: 5px;
outline: none;
display: block;
margin: 0 auto; /* wyśrodkowanie w rzędzie */
}
/* Radiobrowser */
.search-bar {
margin-top: 18px;
display: flex;
flex-wrap: wrap;
gap: 6px;
}
.search-bar input[type="text"],
.search-bar input[type="number"],
.search-bar select {
padding: 5px 7px;
border-radius: 6px;
border: 1px solid var(--border);
background: #000;
color: var(--fg);
font-size: 13px;
}
.search-bar button {
padding: 6px 10px;
border-radius: 6px;
border: 1px solid var(--border);
background: var(--accent-soft);
color: var(--fg);
cursor: pointer;
font-size: 13px;
white-space: nowrap;
}
.search-bar button:hover {
background: var(--accent);
}
.status {
font-size: 12px;
color: #aaa;
margin-top: 6px;
}
.results {
max-height: 260px;
overflow-y: auto;
border-top: 1px solid var(--border);
margin-top: 6px;
padding-top: 4px;
}
.station {
display: flex;
gap: 8px;
align-items: center;
padding: 6px 4px;
border-bottom: 1px solid #222;
}
.station:nth-child(even) {
background: #151515;
}
.station-meta-box {
flex: 1;
}
.station-name {
font-size: 13px;
font-weight: 600;
}
.station-meta {
font-size: 11px;
color: #aaa;
}
.station-url {
font-size: 11px;
word-break: break-all;
color: #ccc;
}
.station-actions {
display: flex;
flex-direction: column;
gap: 3px;
}
.btn-small {
font-size: 11px;
padding: 3px 6px;
border-radius: 5px;
border: 1px solid var(--border);
background: #333;
color: var(--fg);
cursor: pointer;
white-space: nowrap;
}
.btn-small:hover { background: #444; }
@media (max-width: 640px) {
.eq-slider-wrap { height: 100px; }
}
</style>
</head>
<body>
<header>
<h1>EVO – Radiobrowser + EQ16</h1>
<div class="host">Host: <span id="hostInfo">?</span></div>
</header>
<main>
<div class="card">
<!-- ANALIZATOR -->
<div class="analyzer-header">
<h2>Analizator widma (wewn trzny EQ16)</h2>
</div>
<div class="analyzer-desc">
Pasek pokazuje aktualne pasma z analizatora w firmware
(odczyt z <code>/api/eq/spectrum</code>). Po bokach skala w dB.
</div>
<canvas id="spectrumCanvas"></canvas>
<div id="anFreqLabels" class="an-freq-labels"></div>
<!-- EQ -->
<div class="eq-header">
<h2>EQ16 – korektor graficzny</h2>
<div class="eq-toggle-group">
<label class="eq-toggle">
<input type="checkbox" id="eqEnabled">
<span>Korektor</span>
</label>
<label class="eq-toggle">
<input type="checkbox" id="anEnabled">
<span>Analizator</span>
</label>
</div>
</div>
<div class="eq-info">
16 pasm 20 Hz – 20 kHz, zakres -18 … +18 dB.
Zmiany wysy ane przez API do EVO (<code>/api/eq/*</code>).
</div>
<div id="eqSliders" class="eq-sliders"></div>
<div class="eq-buttons">
<button type="button" onclick="setAll(0)">P asko (0 dB)</button>
<button type="button" onclick="setAll(-6)">Wyciszenie pasm (-6 dB)</button>
<button type="button" onclick="setAll(6)">Podbicie pasm (+6 dB)</button>
<button type="button" onclick="applyPreset('rock')">Preset ROCK</button>
<button type="button" onclick="applyPreset('disco')">Preset DISCO</button>
<button type="button" onclick="applyPreset('pop')">Preset POP</button>
<button type="button" onclick="reloadFromDevice()">Odczytaj z EVO</button>
<button type="button" onclick="saveEQToDevice()">Zapisz EQ</button>
</div>
<!-- Volume -->
<div class="vol-section">
<div class="vol-label-row">
<span>G o no </span>
<span id="volValue">--</span>
</div>
<input type="range"
id="volSlider"
class="vol-slider"
min="1" max="42" step="1" value="1">
</div>
<!-- Radiobrowser -->
<h2 style="margin-top:18px;">Radiobrowser – wyszukiwarka stacji</h2>
<div class="search-bar">
<input id="q" type="text" placeholder="Nazwa / kraj">
<input id="tag" type="text" placeholder="Tag (rock, 80s...)">
<input id="minBr" type="number" placeholder="Min kbps" min="0" step="32">
<select id="limit">
<option value="20">20</option>
<option value="50" selected>50</option>
<option value="100">100</option>
</select>
<button id="btnSearch">Szukaj</button>
</div>
<div id="status" class="status">Podaj fraz i kliknij „Szukaj”.</div>
<div id="results" class="results"></div>
</div>
</main>
<script>
// Host info
document.getElementById('hostInfo').textContent =
window.location.hostname || 'localhost';
// ------- ANALIZATOR -------
const specCanvas = document.getElementById('spectrumCanvas');
const specCtx = specCanvas.getContext('2d');
// zakres wy wietlania skali
const DISPLAY_MIN_DB = -60;
const DISPLAY_MAX_DB = 6;
// peak-hold
let peakLevels = [];
let peakHold = [];
let peakAlpha = [];
const PEAK_HOLD_FRAMES = 1;
const PEAK_FALL_STEP = 0.12;
const PEAK_FADE_FACTOR = 0.6;
function resizeSpectrumCanvas() {
const rect = specCanvas.getBoundingClientRect();
specCanvas.width = rect.width;
specCanvas.height = rect.height;
}
window.addEventListener('resize', resizeSpectrumCanvas);
resizeSpectrumCanvas();
function drawSpectrum(levels) {
const ctx = specCtx;
const w = specCanvas.width;
const h = specCanvas.height;
ctx.fillStyle = '#000';
ctx.fillRect(0, 0, w, h);
const marginLeft = 32;
const marginRight = 32;
const paddingTop = 10;
const paddingBottom = 20;
const innerW = w - marginLeft - marginRight;
const innerH = h - paddingTop - paddingBottom;
// siatka dB ( cznie dodatnie warto ci)
const dbTicks = [6, 0, -10, -20, -30, -40, -50, -60];
ctx.strokeStyle = '#333';
ctx.lineWidth = 1;
ctx.font = '9px system-ui, sans-serif';
ctx.fillStyle = '#888';
ctx.textBaseline = 'middle';
dbTicks.forEach(db => {
let norm = (db - DISPLAY_MIN_DB) / (DISPLAY_MAX_DB - DISPLAY_MIN_DB);
if (norm < 0) norm = 0;
if (norm > 1) norm = 1;
const y = paddingTop + innerH - norm * innerH;
ctx.beginPath();
ctx.moveTo(marginLeft, y);
ctx.lineTo(w - marginRight, y);
ctx.stroke();
const label = (db > 0 ? '+' : '') + db + ' dB';
ctx.textAlign = 'right';
ctx.fillText(label, marginLeft - 4, y);
ctx.textAlign = 'left';
ctx.fillText(label, w - marginRight + 4, y);
});
if (!levels || !levels.length) return;
const n = levels.length;
if (peakLevels.length !== n) {
peakLevels = new Array(n).fill(0);
peakHold = new Array(n).fill(0);
peakAlpha = new Array(n).fill(0);
}
const barGap = 3;
const totalGap = barGap * (n + 1);
const barWidth = Math.max(3, (innerW - totalGap) / n);
const steps = 18;
for (let i = 0; i < n; i++) {
// z firmware dostajemy 0..1 odpowiadaj ce -60..0 dB
const vIn = levels[i];
let dbVal = vIn * 60 - 60; // -60..0
if (dbVal < -60) dbVal = -60;
if (dbVal > 0) dbVal = 0;
// przeskalowanie do zakresu -60..+6 (wizualnie)
let v = (dbVal - DISPLAY_MIN_DB) / (DISPLAY_MAX_DB - DISPLAY_MIN_DB);
if (v < 0) v = 0;
if (v > 1) v = 1;
// peak-hold
if (v >= peakLevels[i]) {
peakLevels[i] = v;
peakHold[i] = 0;
peakAlpha[i] = 1.0;
} else {
if (peakHold[i] < PEAK_HOLD_FRAMES) {
peakHold[i]++;
} else {
peakLevels[i] = Math.max(0, peakLevels[i] - PEAK_FALL_STEP);
peakAlpha[i] = Math.max(0, peakAlpha[i] * PEAK_FADE_FACTOR);
}
}
const activeSteps = Math.round(v * steps);
const x = marginLeft + barGap + i * (barWidth + barGap);
const stepH = innerH / steps;
// kostki s upka
for (let s = 0; s < steps; s++) {
const y = paddingTop + innerH - (s + 1) * stepH;
if (s < activeSteps) {
const frac = (s + 1) / steps;
if (frac < 0.6) {
ctx.fillStyle = '#2e7d32';
} else if (frac < 0.85) {
ctx.fillStyle = '#f9a825';
} else {
ctx.fillStyle = '#e53935';
}
} else {
ctx.fillStyle = '#111';
}
ctx.fillRect(x, y, barWidth, stepH - 1);
}
// niebieski peak z wygaszaniem
const peakV = peakLevels[i];
const alpha = peakAlpha[i];
if (peakV > 0.001 && alpha > 0.01) {
const peakY = paddingTop + innerH - peakV * innerH;
ctx.save();
ctx.globalAlpha = alpha;
ctx.fillStyle = '#2196f3';
ctx.fillRect(x, peakY - 2, barWidth, 3);
ctx.restore();
}
}
ctx.fillStyle = '#1b5e20';
ctx.fillRect(marginLeft, h - paddingBottom + 4, innerW, 2);
}
async function spectrumLoop() {
const POLL_MS = 120;
while (true) {
let bands = [];
try {
const resp = await fetch('/api/eq/spectrum');
if (resp.ok) {
const data = await resp.json();
if (data && Array.isArray(data.bands)) {
bands = data.bands;
}
}
} catch (e) {
// brak endpointu – nic nie rysujemy
}
drawSpectrum(bands);
await new Promise(r => setTimeout(r, POLL_MS));
}
}
spectrumLoop();
// ------- EQ16 + cz stotliwo ci -------
const EQ_BANDS = 16;
const freqs = [];
(function computeFreqs() {
const f0 = 20, f1 = 20000;
for (let i = 0; i < EQ_BANDS; i++) {
const t = i / (EQ_BANDS - 1);
const f = f0 * Math.pow(f1 / f0, t);
freqs.push(f);
}
})();
const anFreqLabelsDiv = document.getElementById('anFreqLabels');
function createAnalyzerLabels() {
anFreqLabelsDiv.innerHTML = '';
for (let i = 0; i < EQ_BANDS; i++) {
const div = document.createElement('div');
div.className = 'an-freq-label';
const f = freqs[i];
div.textContent = f < 1000 ? Math.round(f) + ' Hz'
: (f / 1000).toFixed(1) + ' k';
anFreqLabelsDiv.appendChild(div);
}
}
const eqSlidersBox = document.getElementById('eqSliders');
const eqEnabledCheck = document.getElementById('eqEnabled');
const anEnabledCheck = document.getElementById('anEnabled');
let eqGains = new Array(EQ_BANDS).fill(0);
function createEqUI() {
eqSlidersBox.innerHTML = '';
for (let i = 0; i < EQ_BANDS; i++) {
const bandDiv = document.createElement('div');
bandDiv.className = 'eq-band';
const wrap = document.createElement('div');
wrap.className = 'eq-slider-wrap';
const slider = document.createElement('input');
slider.type = 'range';
slider.min = '-18';
slider.max = '18';
slider.step = '0.5';
slider.value = '0';
slider.className = 'eq-slider';
slider.dataset.band = String(i);
slider.addEventListener('input', onSliderInput);
slider.addEventListener('change', onSliderChange);
wrap.appendChild(slider);
bandDiv.appendChild(wrap);
const label = document.createElement('div');
label.className = 'eq-band-label';
const f = freqs[i];
label.textContent = f < 1000 ? Math.round(f) + ' Hz'
: (f/1000).toFixed(1) + ' k';
bandDiv.appendChild(label);
const gain = document.createElement('div');
gain.className = 'eq-band-gain';
gain.id = 'eqGain' + i;
gain.textContent = '0 dB';
bandDiv.appendChild(gain);
eqSlidersBox.appendChild(bandDiv);
}
}
function onSliderInput(e) {
const band = parseInt(e.target.dataset.band, 10);
const val = parseFloat(e.target.value);
const gainLabel = document.getElementById('eqGain' + band);
gainLabel.textContent = (val > 0 ? '+' : '') + val.toFixed(1) + ' dB';
}
function onSliderChange(e) {
const band = parseInt(e.target.dataset.band, 10);
const val = parseFloat(e.target.value);
eqGains[band] = val;
sendBandToDevice(band, val);
}
function setAll(v) {
eqGains = eqGains.map(() => v);
const sliders = eqSlidersBox.querySelectorAll('.eq-slider');
sliders.forEach((sl, i) => {
sl.value = v;
const lbl = document.getElementById('eqGain' + i);
lbl.textContent = (v > 0 ? '+' : '') + Number(v).toFixed(1) + ' dB';
});
sendAllToDevice();
}
// Presety ROCK / DISCO / POP
function applyPreset(name) {
let curve = new Array(EQ_BANDS).fill(0);
if (name === 'rock') {
curve = [5, 4, 3, 2, 1, 0, -1, -2, -2, -1, 0, 1, 2, 3, 4, 5];
} else if (name === 'disco') {
curve = [6, 5, 4, 3, 2, 1, 0, -1, -1, 0, 1, 2, 3, 4, 5, 6];
} else if (name === 'pop') {
curve = [3, 2, 1, 0, 0, 0, -1, -1, -1, 0, 1, 2, 3, 3, 3, 3];
}
eqGains = curve.slice(0, EQ_BANDS);
const sliders = eqSlidersBox.querySelectorAll('.eq-slider');
sliders.forEach((sl, i) => {
const g = eqGains[i] || 0;
sl.value = g;
const lbl = document.getElementById('eqGain' + i);
lbl.textContent = (g > 0 ? '+' : '') + g.toFixed(1) + ' dB';
});
sendAllToDevice();
saveEQToDevice();
}
async function reloadFromDevice() {
try {
const resp = await fetch('/api/eq/state');
if (!resp.ok) throw new Error('HTTP ' + resp.status);
const data = await resp.json();
eqEnabledCheck.checked = !!data.enabled;
if ('analyzer' in data) {
anEnabledCheck.checked = !!data.analyzer;
}
if (Array.isArray(data.gains)) {
eqGains = data.gains.slice(0, EQ_BANDS).map(g => parseFloat(g) || 0);
const sliders = eqSlidersBox.querySelectorAll('.eq-slider');
sliders.forEach((sl, i) => {
const g = eqGains[i] || 0;
sl.value = g;
const lbl = document.getElementById('eqGain' + i);
lbl.textContent = (g > 0 ? '+' : '') + g.toFixed(1) + ' dB';
});
}
} catch (e) {
alert('Nie uda o si odczyta EQ z /api/eq/state');
}
}
function sendBandToDevice(band, gain) {
const url = '/api/eq/band?band=' + encodeURIComponent(band) +
'&gain=' + encodeURIComponent(gain.toFixed(2));
fetch(url).catch(() => {});
}
function sendAllToDevice() {
const params = new URLSearchParams();
eqGains.forEach((g, i) => {
params.append('g' + i, g.toFixed(2));
});
fetch('/api/eq/set?' + params.toString()).catch(() => {});
}
async function saveEQToDevice() {
try {
const resp = await fetch('/api/eq/save');
if (!resp.ok) throw new Error('HTTP ' + resp.status);
alert('EQ zapisany do pliku.');
} catch (e) {
alert('B d zapisu EQ: ' + e.message);
}
}
eqEnabledCheck.addEventListener('change', () => {
const on = eqEnabledCheck.checked ? 1 : 0;
fetch('/api/eq/enable?on=' + on).catch(() => {});
});
anEnabledCheck.addEventListener('change', () => {
const on = anEnabledCheck.checked ? 1 : 0;
fetch('/api/eq/analyzer?on=' + on).catch(() => {});
});
createAnalyzerLabels();
createEqUI();
reloadFromDevice();
// ------- VOLUME przez WebSocket -------
const volSlider = document.getElementById('volSlider');
const volValue = document.getElementById('volValue');
let websocket = null;
function setVolumeLabel(v) {
volValue.textContent = v;
}
function updateSliderVolume() {
const sliderValue = volSlider.value;
setVolumeLabel(sliderValue);
if (websocket && websocket.readyState === WebSocket.OPEN) {
websocket.send("volume:" + sliderValue);
} else {
console.warn("WebSocket niepo czony");
}
}
function connectWebSocket() {
websocket = new WebSocket('ws://' + window.location.hostname + '/ws');
websocket.onopen = function () {
console.log("WebSocket po czony (EQ panel)");
};
websocket.onclose = function () {
console.log("WebSocket zamkni ty – reconnect za 3s");
setTimeout(connectWebSocket, 3000);
};
websocket.onerror = function (error) {
console.error("B d WebSocket:", error);
websocket.close();
};
websocket.onmessage = function (event) {
if (event.data.startsWith("volume:")) {
const vol = parseInt(event.data.split(":")[1]);
if (!isNaN(vol)) {
volSlider.value = vol;
setVolumeLabel(vol);
}
}
};
}
volSlider.addEventListener('input', () => {
setVolumeLabel(volSlider.value);
});
volSlider.addEventListener('change', () => {
updateSliderVolume();
});
volSlider.addEventListener("wheel", function (event) {
event.preventDefault();
let currentValue = parseInt(volSlider.value);
const step = parseInt(volSlider.step) || 1;
const max = parseInt(volSlider.max);
const min = parseInt(volSlider.min);
if (event.deltaY < 0) {
volSlider.value = Math.min(currentValue + step, max);
} else {
volSlider.value = Math.max(currentValue - step, min);
}
updateSliderVolume();
});
connectWebSocket();
// ------- Radiobrowser -------
const statusEl = document.getElementById('status');
const resultsEl = document.getElementById('results');
const API_RB = 'https://de1.api.radio-browser.info/json/stations/search';
document.getElementById('btnSearch').addEventListener('click', searchStations);
document.getElementById('q').addEventListener('keydown', e => {
if (e.key === 'Enter') searchStations();
});
async function searchStations() {
const q = document.getElementById('q').value.trim();
const tag = document.getElementById('tag').value.trim();
const minBr = document.getElementById('minBr').value.trim();
const limit = document.getElementById('limit').value;
if (!q && !tag) {
statusEl.textContent = 'Podaj przynajmniej nazw lub tag.';
return;
}
statusEl.textContent = 'Szukam...';
resultsEl.innerHTML = '';
const params = new URLSearchParams();
if (q) params.append('name', q);
if (tag) params.append('tag', tag);
params.append('hidebroken', 'true');
params.append('limit', limit);
if (minBr) params.append('bitrate_min', minBr);
try {
const resp = await fetch(API_RB + '?' + params.toString());
if (!resp.ok) throw new Error('HTTP ' + resp.status);
const data = await resp.json();
if (!Array.isArray(data) || data.length === 0) {
statusEl.textContent = 'Brak wynik w.';
return;
}
statusEl.textContent = 'Znaleziono ' + data.length + ' stacji.';
data.forEach(addStationRow);
} catch (e) {
statusEl.textContent = 'B d pobierania: ' + e.message;
}
}
function addStationRow(s) {
const div = document.createElement('div');
div.className = 'station';
const metaBox = document.createElement('div');
metaBox.className = 'station-meta-box';
const name = document.createElement('div');
name.className = 'station-name';
name.textContent = s.name || '(bez nazwy)';
const meta = document.createElement('div');
meta.className = 'station-meta';
const country = s.country || '';
const br = s.bitrate ? s.bitrate + ' kbps' : '';
const codec = s.codec || '';
meta.textContent = [country, br, codec].filter(Boolean).join(' • ');
const url = document.createElement('div');
url.className = 'station-url';
url.textContent = s.url_resolved || s.url || '';
metaBox.appendChild(name);
metaBox.appendChild(meta);
metaBox.appendChild(url);
const actions = document.createElement('div');
actions.className = 'station-actions';
const btnSet = document.createElement('button');
btnSet.className = 'btn-small';
btnSet.textContent = 'Ustaw w EVO';
btnSet.onclick = () => sendToEvo(s);
actions.appendChild(btnSet);
const btnCopy = document.createElement('button');
btnCopy.className = 'btn-small';
btnCopy.textContent = 'Kopiuj URL';
btnCopy.onclick = () => copyUrl(url.textContent);
actions.appendChild(btnCopy);
div.appendChild(metaBox);
div.appendChild(actions);
resultsEl.appendChild(div);
}
function copyUrl(url) {
if (!url) return;
navigator.clipboard.writeText(url)
.then(() => statusEl.textContent = 'Skopiowano URL.')
.catch(() => statusEl.textContent = 'Nie uda o si skopiowa .');
}
function sendToEvo(st) {
const url = st.url_resolved || st.url || '';
if (!url) return;
const name = st.name || 'Radio';
const href = '/update?url=' + encodeURIComponent(url) +
'&name=' + encodeURIComponent(name);
statusEl.textContent = 'Wysy am URL do EVO...';
fetch(href)
.then(() => statusEl.textContent = 'URL wys any.')
.catch(() => statusEl.textContent = 'B d wysy ania URL.');
}
</script>
</body>
</html>
ArturAVS wrote:my policy is not to tamper with the factory front panel.
DJCheester wrote:And tell me if maybe you changed something in the speed of connection of the radio to the wifi and the time until the stream starts playing. And kol Robgold it happens very fast, at you this waiting time sometimes is in minutes ?
MAJSTER XXL wrote:I have described this before, with me the v3 radio starts in a few seconds, this long connection problem is caused by substituted libesp_netif.a liblwip.a files Try to restore the correct ones by e.g. uninstalling the esp32 version in the Board Manager in the Arduino IDE and reinstalling the latest version 3.3.3.
MAJSTER XXL wrote:on my v3 radio starts in a few seconds, this long connection problem is caused by swapped libesp_netif.a liblwip.a files
simw wrote:MAJSTER XXL wrote:at my radio v3 starts in a few seconds, this long connection problem is caused by swapped libesp_netif.a liblwip.a files
In my own case, I cannot confirm this.
In the last tabs there was an up-to-date description of the installation with the swapped files, which I used and the radio starts a few seconds.
DJCheester wrote:Did you use this pdf manual ?
simw wrote:Creates the entire environment in a separate directory, so I currently have versions 3.18 and 3.19 separately.
You can independently "develop" versions for yourself.
DJCheester wrote:Are not these version portals using the Arduino15 directory where the core is downloaded ?
simw wrote:The whole thing takes up almost 7.4 GiB once I have adapted to compile version 3.19.33.
robgold wrote:as a matter of curiosity you can check the stream of our home station smoothjazz.com.pl