Header com Player e VU Meter
const STREAM_URL = "http://stream.plusturbo.com.br:1120/stream";
const playBtn = document.getElementById('playBtn');
const volumeSlider = document.getElementById('volumeSlider');
const canvas = document.getElementById('vuCanvas');
const ctx = canvas.getContext('2d');
let audioCtx, audioSource, analyser, audio;
let isPlaying = false;
let animationFrameId;
// Física do Ponteiro
let currentPointerAngle = Math.PI; // Começa na esquerda (mínimo)
let targetPointerAngle = Math.PI;
function initAudio() {
audioCtx = new (window.AudioContext || window.webkitAudioContext)();
analyser = audioCtx.createAnalyser();
analyser.fftSize = 256;
audio = new Audio();
audio.crossOrigin = "anonymous";
audio.src = STREAM_URL;
audioSource = audioCtx.createMediaElementSource(audio);
audioSource.connect(analyser);
analyser.connect(audioCtx.destination);
}
playBtn.addEventListener('click', () => {
if (!audioCtx) initAudio();
if (audioCtx.state === 'suspended') {
audioCtx.resume();
}
if (!isPlaying) {
audio.play().then(() => {
isPlaying = true;
playBtn.textContent = 'PAUSE';
updateVU();
}).catch(err => console.error("Erro ao reproduzir streaming:", err));
} else {
audio.pause();
isPlaying = false;
playBtn.textContent = 'PLAY';
}
});
volumeSlider.addEventListener('input', (e) => {
if (audio) audio.volume = e.target.value;
});
function drawVUMeter(level) {
ctx.clearRect(0, 0, canvas.width, canvas.height);
const centerX = canvas.width / 2;
const centerY = canvas.height - 5;
const radius = canvas.height - 15;
// Desenha o arco de fundo
ctx.beginPath();
ctx.arc(centerX, centerY, radius, Math.PI, 2 * Math.PI, false);
ctx.lineWidth = 3;
ctx.strokeStyle = '#444';
ctx.stroke();
// Desenha a zona vermelha (pico)
ctx.beginPath();
ctx.arc(centerX, centerY, radius, 1.8 * Math.PI, 2 * Math.PI, false);
ctx.lineWidth = 4;
ctx.strokeStyle = '#ff3333';
ctx.stroke();
// Cálculo suavizado da física do ponteiro (filtro passa-baixas simples)
// Mapeia o nível (0 a 255) para o ângulo do arco (De PI até 2*PI)
const targetAngle = Math.PI + (level / 255) * Math.PI;
// Suavização do movimento (inércia do ponteiro)
currentPointerAngle += (targetAngle - currentPointerAngle) * 0.2;
// Desenha o Ponteiro
const pointerX = centerX + radius * Math.cos(currentPointerAngle);
const pointerY = centerY + radius * Math.sin(currentPointerAngle);
ctx.beginPath();
ctx.moveTo(centerX, centerY);
ctx.lineTo(pointerX, pointerY);
ctx.lineWidth = 2;
ctx.strokeStyle = isPlaying ? '#ff5500' : '#888';
ctx.stroke();
// Pino central do ponteiro
ctx.beginPath();
ctx.arc(centerX, centerY, 5, 0, 2 * Math.PI);
ctx.fillStyle = '#111';
ctx.fill();
}
function updateVU() {
if (!isPlaying) {
// Se pausado, faz o ponteiro cair suavemente para o zero
drawVUMeter(0);
if (Math.abs(currentPointerAngle - Math.PI) > 0.01) {
requestAnimationFrame(updateVU);
}
return;
}
const dataArray = new Uint8Array(analyser.frequencyBinCount);
analyser.getByteFrequencyData(dataArray);
// Calcula a média das frequências mais baixas/médias para melhor resposta visual
let total = 0;
const sampleSize = Math.floor(dataArray.length * 0.7); // Foca nos graves/médios
for (let i = 0; i < sampleSize; i++) {
total += dataArray[i];
}
const average = total / sampleSize;
// Multiplicador de sensibilidade (ajustável)
const sensitivity = 1.3;
const level = Math.min(255, average * sensitivity);
drawVUMeter(level);
requestAnimationFrame(updateVU);
}
// Desenho inicial (ponteiro em repouso)
drawVUMeter(0);