
Rádio Azul
Agora na Rádio Azul

Informação, desporto, música e entretenimento.
Ouça-nos em qualquer lugar, a qualquer hora.
As mais tocadas na nossa emissão.
/** /** * player.js * Módulo completo do player de áudio com Media Session API * * Como usar: * 1. Incluir este script na página. * 2. Garantir que existem os elementos HTML com os IDs indicados. * 3. Chamar Player.init() após o DOM estar carregado. * * Elementos HTML necessários (IDs): * - #radioStream :
const Player = (() => { // --- Estado interno --- let audio = null; let audioCtx = null; let analyser = null; let sourceNode = null; let isPlaying = false; let lastSongTitle = ''; let currentMusic = { titulo: 'Rádio Azul', artista: 'Emissão em direto', capa: '' }; let currentProgram = { nome: 'A Rádio de Todos os Sons', locutor: 'Rádio Azul' }; let isProgramaAtivo = false; let refreshInterval = null;
// URLs das APIs (ajusta conforme o teu site) const API_MUSICA = 'https://radioazul.pt/admin/api_musica.php'; const STREAM_URL = 'https://sado.radioazul.pt/live?type=.mp3';
// --- Referências aos elementos DOM (preenchidas no init) --- let elements = {};
// --- Funções auxiliares --- function getElement(id) { const el = document.getElementById(id); if (!el) console.warn(`Elemento #${id} não encontrado`); return el; }
function showToast(msg) { // Podes substituir por um sistema de toasts próprio console.log('[Player]', msg); const toast = document.getElementById('toast'); if (toast) { toast.textContent = msg; toast.style.display = 'block'; setTimeout(() => toast.style.display = 'none', 3000); } }
// --- Media Session --- function updateMediaSession(metadata) { if ('mediaSession' in navigator) { navigator.mediaSession.metadata = new MediaMetadata(metadata); navigator.mediaSession.playbackState = isPlaying ? 'playing' : 'paused'; } }
function setMediaSessionForMusic(title, artist, artworkUrl) { updateMediaSession({ title: title || 'Rádio Azul', artist: artist || 'Emissão em direto', album: 'Rádio Azul 98.9 FM', artwork: artworkUrl ? [{ src: artworkUrl, sizes: '512x512', type: 'image/png' }] : [] }); }
function setMediaSessionForProgram(programa, locutor, horario) { updateMediaSession({ title: programa || 'Rádio Azul', artist: `${locutor || 'Equipa'} • ${horario || ''}`, album: 'Rádio Azul 98.9 FM - Programação', artwork: [{ src: 'https://i.postimg.cc/PrMhmWMz/logo.png', sizes: '512x512', type: 'image/png' }] }); }
// --- Áudio Context e Visualizador --- function initAudioContext() { if (audioCtx) return; audioCtx = new (window.AudioContext || window.webkitAudioContext)(); analyser = audioCtx.createAnalyser(); analyser.fftSize = 256; if (sourceNode) sourceNode.disconnect(); sourceNode = audioCtx.createMediaElementSource(audio); sourceNode.connect(analyser); analyser.connect(audioCtx.destination); drawVisualizer(); }
function drawVisualizer() { if (!analyser) return; const canvas = elements.visualizer; if (!canvas) return; const ctx = canvas.getContext('2d'); const resizeCanvas = () => { const parentWidth = canvas.parentElement ? canvas.parentElement.clientWidth : window.innerWidth - 40; canvas.width = parentWidth; canvas.height = 50; }; resizeCanvas(); window.addEventListener('resize', resizeCanvas); const dataArray = new Uint8Array(analyser.frequencyBinCount); function draw() { if (!analyser) return; analyser.getByteFrequencyData(dataArray); ctx.clearRect(0, 0, canvas.width, canvas.height); const barWidth = canvas.width / dataArray.length; for (let i = 0; i < dataArray.length; i++) { const value = dataArray[i]; const barHeight = canvas.height * (value / 255); ctx.fillStyle = `rgb(255, ${255 - value}, 100)`; ctx.fillRect(i * barWidth, canvas.height - barHeight, barWidth * 0.8, barHeight); } requestAnimationFrame(draw); } draw(); } // --- Controles de reprodução --- function play() { if (!audio) return; audio.play() .then(() => { isPlaying = true; updatePlayButton(); updateStatus('🔊 A tocar'); if (!audioCtx) { initAudioContext(); audioCtx.resume().catch(() => {}); } else if (audioCtx.state === 'suspended') { audioCtx.resume(); } if ('mediaSession' in navigator) { navigator.mediaSession.playbackState = 'playing'; } showToast('▶️ A reproduzir'); }) .catch(err => { showToast('⚠️ Clique novamente para ouvir'); console.warn('Erro ao reproduzir:', err); }); }
function pause() { if (!audio) return; audio.pause(); isPlaying = false; updatePlayButton(); updateStatus('⏸ Pausado'); if ('mediaSession' in navigator) { navigator.mediaSession.playbackState = 'paused'; } }
function togglePlay() { if (isPlaying) { pause(); } else { play(); } }
function updatePlayButton() { const btn = elements.playBtn; if (btn) { btn.innerHTML = isPlaying ? '' : ''; } // Mini player const miniBtn = document.getElementById('miniPlayBtn'); if (miniBtn) { miniBtn.innerHTML = isPlaying ? '' : ''; } }
function updateStatus(text) { const status = elements.statusText; if (status) status.textContent = text; }
// --- Actualização da música actual (via API) --- async function obterMusicaAtual() { try { const res = await fetch(`${API_MUSICA}?action=current`, { cache: 'no-store' }); const data = await res.json(); if (data && data.titulo && data.artista) { return { titulo: data.titulo, artista: data.artista, capa: data.capa || null }; } } catch (e) { console.warn('Erro ao obter música atual:', e); } return { titulo: 'Rádio Azul', artista: 'Emissão em direto', capa: null }; }
async function getCapaFromServer(artista, titulo) { if (!artista || !titulo) return null; try { const url = `${API_MUSICA}?action=get_capa&artista=${encodeURIComponent(artista)}&titulo=${encodeURIComponent(titulo)}`; const res = await fetch(url); const data = await res.json(); return data.capa || null; } catch (e) { return null; } }
// Função chamada periodicamente para actualizar a música em direto async function refreshMusica() { const musica = await obterMusicaAtual(); const tituloCompleto = `${musica.artista} - ${musica.titulo}`; const isDefault = (musica.titulo === 'Rádio Azul' && musica.artista === 'Emissão em direto');
// Só actualiza se a música mudou if (lastSongTitle !== tituloCompleto) { lastSongTitle = tituloCompleto; currentMusic = { titulo: musica.titulo, artista: musica.artista, capa: musica.capa };
// Actualiza elementos DOM if (elements.currentSong) elements.currentSong.textContent = musica.titulo; if (elements.currentArtist) elements.currentArtist.textContent = musica.artista;
// Capa let capaUrl = musica.capa; if (!isDefault && musica.artista && musica.titulo) { const serverCapa = await getCapaFromServer(musica.artista, musica.titulo); if (serverCapa) capaUrl = serverCapa; } if (!capaUrl) capaUrl = 'https://i.postimg.cc/PrMhmWMz/logo.png'; if (elements.coverImg) elements.coverImg.src = capaUrl; currentMusic.capa = capaUrl;
// Actualiza a área "Agora na Rádio Azul" (se existir)
if (elements.musicaDisplay) {
if (!isDefault) {
isProgramaAtivo = false;
elements.musicaDisplay.innerHTML = `${musica.titulo}
${musica.artista}`;
setMediaSessionForMusic(musica.titulo, musica.artista, capaUrl);
} else {
// Quando não há música, mostra o programa actual
isProgramaAtivo = true;
const prog = getCurrentProgramFromSchedule(); // ver abaixo
if (prog) {
elements.musicaDisplay.innerHTML = `${prog.program}
${prog.host}`;
setMediaSessionForProgram(prog.program, prog.host, `${prog.start}-${prog.end}`);
}
if (elements.coverImg) elements.coverImg.src = 'https://i.postimg.cc/PrMhmWMz/logo.png';
}
}
// Sincroniza mini player (se existir) updateMiniPlayer();
// Adiciona ao histórico local (opcional) if (!isDefault) { addToRecent(musica.titulo, musica.artista, capaUrl); // Podes também chamar uma função para guardar no servidor fetch(`${API_MUSICA}?action=add_history`, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ titulo: musica.titulo, artista: musica.artista, capa: capaUrl }) }).catch(() => {}); }
// Notificação if (!isDefault) { showToast(`🎵 ${musica.artista} - ${musica.titulo}`); } } }
// --- Programação (simplificada) - podes substituir pela tua lógica --- function getCurrentProgramFromSchedule() { // Se tiveres uma variável global com a programação, usa-a // Exemplo simples: retorna um programa genérico return { program: 'Emissão online', host: 'Rádio Azul', start: '--:--', end: '--:--' }; }
// --- Mini Player (sincroniza com o player principal) --- function updateMiniPlayer() { const miniSong = document.getElementById('miniSong'); const miniArtist = document.getElementById('miniArtist'); const miniCover = document.getElementById('miniCover'); if (miniSong && elements.currentSong) miniSong.textContent = elements.currentSong.textContent; if (miniArtist && elements.currentArtist) miniArtist.textContent = elements.currentArtist.textContent; if (miniCover && elements.coverImg) miniCover.src = elements.coverImg.src; const miniPlayBtn = document.getElementById('miniPlayBtn'); if (miniPlayBtn) { miniPlayBtn.innerHTML = isPlaying ? '' : ''; } }
// --- Histórico recente (opcional) --- function addToRecent(title, artist, capa) { let recent = JSON.parse(localStorage.getItem('recent_tracks') || '[]'); if (recent.length && recent[0].title === title && recent[0].artist === artist) return; recent.unshift({ title, artist, time: new Date().toLocaleTimeString(), capa: capa || 'https://i.postimg.cc/PrMhmWMz/logo.png' }); if (recent.length > 10) recent.pop(); localStorage.setItem('recent_tracks', JSON.stringify(recent)); // Se tiveres um elemento para mostrar as recentes, actualiza-o renderRecentList(); }
function renderRecentList() { const container = document.getElementById('recentList'); if (!container) return; const recent = JSON.parse(localStorage.getItem('recent_tracks') || '[]'); if (!recent.length) { container.innerHTML = '
Nenhuma música ouvida ainda.
'; return; } container.innerHTML = recent.map(t => `
`).join(''); }
// --- Configuração de eventos --- function setupEventListeners() { // Play/Pause if (elements.playBtn) { elements.playBtn.addEventListener('click', togglePlay); } // Mini play const miniPlayBtn = document.getElementById('miniPlayBtn'); if (miniPlayBtn) { miniPlayBtn.addEventListener('click', togglePlay); } // Volume if (elements.volumeSlider) { elements.volumeSlider.addEventListener('input', (e) => { if (audio) audio.volume = parseFloat(e.target.value); const miniVol = document.getElementById('miniVolume'); if (miniVol) miniVol.value = e.target.value; localStorage.setItem('radio_volume', e.target.value); }); const savedVol = localStorage.getItem('radio_volume'); if (savedVol) { elements.volumeSlider.value = savedVol; if (audio) audio.volume = parseFloat(savedVol); } } const miniVol = document.getElementById('miniVolume'); if (miniVol) { miniVol.addEventListener('input', (e) => { if (audio) audio.volume = parseFloat(e.target.value); if (elements.volumeSlider) elements.volumeSlider.value = e.target.value; }); } // Botão de reset (opcional) const resetBtn = document.getElementById('resetCacheBtn'); if (resetBtn) { resetBtn.addEventListener('click', () => { if (confirm('Limpar todos os dados?')) { localStorage.clear(); location.reload(); } }); } // Botões de acção (copiar, partilhar, like) - podes adaptar setupActionButtons(); }
function setupActionButtons() { const copyBtn = document.getElementById('copyMusicBtn'); if (copyBtn) { copyBtn.addEventListener('click', () => { const song = elements.currentSong?.textContent || ''; const artist = elements.currentArtist?.textContent || ''; navigator.clipboard.writeText(`${song} - ${artist}`); showToast('Copiado!'); }); } const shareBtn = document.getElementById('shareMusicBtn'); if (shareBtn) { shareBtn.addEventListener('click', async () => { const song = elements.currentSong?.textContent || ''; const artist = elements.currentArtist?.textContent || ''; const text = `A ouvir: ${song} - ${artist} na Rádio Azul`; if (navigator.share) { try { await navigator.share({ text }); } catch (e) {} } else { navigator.clipboard.writeText(text); showToast('Partilha copiada!'); } }); } const likeBtn = document.getElementById('likeBtn'); if (likeBtn) { likeBtn.addEventListener('click', () => { const song = elements.currentSong?.textContent || ''; const artist = elements.currentArtist?.textContent || ''; if (song && artist && song !== 'Rádio Azul' && artist !== 'Emissão em direto') { // Chama a API de like fetch(`${API_MUSICA}?action=like`, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ titulo: song, artista: artist }) }).then(() => showToast('👍 Like registado!')).catch(() => showToast('Erro ao dar like')); } else { showToast('Apenas pode dar like a músicas em directo'); } }); } }
// --- Inicialização pública --- function init() { // Guarda referências dos elementos elements = { audio: document.getElementById('radioStream'), playBtn: document.getElementById('playBtn'), volumeSlider: document.getElementById('volumeSlider'), statusText: document.getElementById('statusText'), coverImg: document.getElementById('coverImg'), currentSong: document.getElementById('currentSong'), currentArtist: document.getElementById('currentArtist'), visualizer: document.getElementById('visualizer'), musicaDisplay: document.getElementById('musicaDisplay'), progAtual: document.getElementById('progAtual'), progSeguinte: document.getElementById('progSeguinte'), };
audio = elements.audio; if (!audio) { console.error('Elemento
// Restaura volume const savedVol = localStorage.getItem('radio_volume'); if (savedVol) { audio.volume = parseFloat(savedVol); if (elements.volumeSlider) elements.volumeSlider.value = savedVol; }
// Configura eventos setupEventListeners();
// Inicia o visualizador quando o utilizador clicar no player (audio context) // O visualizador só é iniciado no primeiro play
// Carrega música inicial refreshMusica();
// Actualiza a música a cada 5 segundos if (refreshInterval) clearInterval(refreshInterval); refreshInterval = setInterval(refreshMusica, 5000);
// Sincroniza com Media Session actions (opcional) if ('mediaSession' in navigator) { navigator.mediaSession.setActionHandler('play', play); navigator.mediaSession.setActionHandler('pause', pause); navigator.mediaSession.setActionHandler('stop', pause); // Podes adicionar seek forward/backward se quiseres }
// Mostra as recentes renderRecentList();
console.log('Player inicializado com sucesso!'); }
// --- API pública --- return { init, play, pause, togglePlay, refreshMusica, isPlaying: () => isPlaying, getCurrentMusic: () => ({ ...currentMusic }), }; })();
// Inicializa quando o DOM estiver pronto document.addEventListener('DOMContentLoaded', Player.init);