1const RADIO_NAME = 'Shabiba FM'; 2 3// Change Stream URL Here, Supports, ICECAST, ZENO, SHOUTCAST, RADIOJAR and any other stream service. 4const URL_STREAMING = 'https://stream.shabiba.fm:9010/shabiba'; 5 6//API URL / 7// const API_URL = 'https://twj.es/free/?url='+URL_STREAMING; 8// const FALLBACK_API_URL = 'https://twj.es/metadata/?url=' + URL_STREAMING; 9 10// Visit https://api.vagalume.com.br/docs/ to get your API key 11// const API_KEY = "18fe07917957c289983464588aabddfb"; 12 13let userInteracted = true; 14 15let musicaAtual = null; 16 17// Cache para a API do iTunes 18const cache = {}; 19 20window.addEventListener('load', () => { 21 const page = new Page(); 22 page.changeTitlePage(); 23 page.setVolume(); 24 25 const player = new Player(); 26 player.play(); 27 28 // Chama a função getStreamingData imediatamente quando a página carrega 29 getStreamingData(); 30 31 // Define o intervalo para atualizar os dados de streaming a cada 10 segundos 32 const streamingInterval = setInterval(getStreamingData, 10000); 33 34 // Ajusta a altura da capa do álbum para ser igual à sua largura 35 const coverArt = document.querySelector('.cover-album'); // Use querySelector para selecionar o elemento 36 if (coverArt) { 37 // Adiciona uma verificação para garantir que o elemento exista 38 coverArt.style.height = `${coverArt.offsetWidth}px`; 39 } else { 40 console.warn("Elemento .cover-album não encontrado."); 41 } 42}); 43 44// DOM control 45class Page { 46 constructor() { 47 this.changeTitlePage = function (title = RADIO_NAME) { 48 document.title = title; 49 }; 50 51 this.refreshCurrentSong = function(song, artist) { 52 const currentSong = document.getElementById('currentSong'); 53 const currentArtist = document.getElementById('currentArtist'); 54 const lyricsSong = document.getElementById('lyricsSong'); 55 56 if (song !== currentSong.textContent || artist !== currentArtist.textContent) { 57 // Esmaecer o conteúdo existente (fade-out) 58 currentSong.classList.add('fade-out'); 59 currentArtist.classList.add('fade-out'); 60 61 setTimeout(function() { 62 // Atualizar o conteúdo após o fade-out 63 currentSong.textContent = song; 64 currentArtist.textContent = artist; 65 lyricsSong.textContent = song + ' - ' + artist; 66 67 // Esmaecer o novo conteúdo (fade-in) 68 currentSong.classList.remove('fade-out'); 69 currentSong.classList.add('fade-in'); 70 currentArtist.classList.remove('fade-out'); 71 currentArtist.classList.add('fade-in'); 72 }, 500); 73 74 setTimeout(function() { 75 // Remover as classes fade-in após a animação 76 currentSong.classList.remove('fade-in'); 77 currentArtist.classList.remove('fade-in'); 78 }, 1000); 79 } 80 }; 81 82 this.refreshHistoric = async function (info, n) { 83 const historicDiv = document.querySelectorAll("#historicSong article")[n]; 84 const songName = document.querySelectorAll("#historicSong article .music-info .song")[n]; 85 const artistName = document.querySelectorAll("#historicSong article .music-info .artist")[n]; 86 const coverHistoric = document.querySelectorAll("#historicSong article .cover-historic")[n]; 87 88 const defaultCoverArt = "img/cover.png"; 89 90 // Extrai o tÃtulo da música e o nome do artista, 91 // tratando a possibilidade de 'song' e 'artist' serem objetos ou strings. 92 const songTitle = typeof info.song === "object" ? info.song.title : info.song; 93 const songArtist = typeof info.artist === "object" ? info.artist.title : info.artist; 94 95 // Define o conteúdo dos elementos HTML, 96 // incluindo uma verificação para evitar erros caso os valores estejam ausentes. 97 songName.innerHTML = songTitle || "Desconhecido"; 98 artistName.innerHTML = songArtist || "Desconhecido"; 99 100 try { 101 // Utiliza os valores extraÃdos para buscar a capa do álbum na API do iTunes. 102 const data = await getDataFromITunes(songArtist, songTitle, defaultCoverArt, defaultCoverArt); 103 // Define a imagem de fundo do elemento 'coverHistoric' com a capa encontrada. 104 coverHistoric.style.backgroundImage = "url(" + (data.art || defaultCoverArt) + ")"; 105 } catch (error) { 106 // Captura e imprime o erro no console para ajudar na depuração. 107 console.log("Erro ao buscar dados da API do iTunes:"); 108 console.error(error); 109 // Define a imagem de fundo como a capa padrão em caso de erro. 110 coverHistoric.style.backgroundImage = "url(" + defaultCoverArt + ")"; 111 } 112 113 // Adiciona a classe 'animated' para a animação de slide. 114 historicDiv.classList.add("animated", "slideInRight"); 115 // Remove a classe 'animated' após 2 segundos para preparar para a próxima animação. 116 setTimeout(() => historicDiv.classList.remove("animated", "slideInRight"), 2000); 117 }; 118 119 this.refreshCover = async function (song = '', artist) { 120 const coverArt = document.getElementById('currentCoverArt');
121 const coverBackground = document.getElementById('bgCover'); 122 const defaultCoverArt = 'img/cover.png'; 123 124 try { 125 const data = await getDataFromITunes(artist, song, defaultCoverArt, defaultCoverArt); 126 127 // Aplica a imagem de capa (sempre, mesmo se for a padrão) 128 coverArt.style.backgroundImage = 'url(' + data.art + ')'; 129 coverBackground.style.backgroundImage = 'url(' + data.cover + ')'; 130 131 // Adiciona/remove classes para animação (se necessário) 132 coverArt.classList.add('animated', 'bounceInLeft'); 133 setTimeout(() => coverArt.classList.remove('animated', 'bounceInLeft'), 2000); 134 135 // Atualiza MediaSession (se suportado) 136 if ('mediaSession' in navigator) { 137 const artwork = [ 138 { src: data.art, sizes: '96x96', type: 'image/png' }, 139 { src: data.art, sizes: '128x128', type: 'image/png' }, 140 { src: data.art, sizes: '192x192', type: 'image/png' }, 141 { src: data.art, sizes: '256x256', type: 'image/png' }, 142 { src: data.art, sizes: '384x384', type: 'image/png' }, 143 { src: data.art, sizes: '512x512', type: 'image/png' }, 144 ]; 145 146 navigator.mediaSession.metadata = new MediaMetadata({ 147 title: song, 148 artist: artist, 149 artwork 150 }); 151 } 152 } catch (error) { 153 console.log("Erro ao buscar dados da API do iTunes:", error); 154 // ... (lógica para lidar com o erro) 155 } 156 }; 157 158 this.changeVolumeIndicator = function(volume) { 159 document.getElementById('volIndicator').textContent = volume; // Use textContent em vez de innerHTML 160 161 if (typeof Storage !== 'undefined') { 162 localStorage.setItem('volume', volume); 163 } 164 }; 165 166 this.setVolume = function() { 167 if (typeof Storage !== 'undefined') { 168 const volumeLocalStorage = localStorage.getItem('volume') || 80; // Operador de coalescência nula (??) 169 170 document.getElementById('volume').value = volumeLocalStorage; 171 document.getElementById('volIndicator').textContent = volumeLocalStorage; 172 } 173 }; 174 175 this.refreshLyric = async function (currentSong, currentArtist) { 176 const openLyric = document.getElementsByClassName('lyrics')[0]; 177 const modalLyric = document.getElementById('modalLyrics'); 178 179 try { 180 const response = await fetch('https://api.vagalume.com.br/search.php?apikey=' + API_KEY + '&art=' + currentArtist + '&mus=' + currentSong.toLowerCase()); 181 const data = await response.json(); 182 183 if (data.type === 'exact' || data.type === 'aprox') { 184 const lyric = data.mus[0].text; 185 186 //document.getElementById('lyric').textContent = lyric.replace(/\n/g, '<br />'); Use textContent em vez de innerHTML 187 document.getElementById('lyric').innerHTML = lyric.replace(/\n/g, '<br />'); 188 openLyric.style.opacity = "1"; 189 openLyric.setAttribute('data-toggle', 'modal'); 190 191 // Esconde o modal caso esteja visÃvel 192 modalLyric.style.display = "none"; 193 modalLyric.setAttribute('aria-hidden', 'true'); 194 if (document.getElementsByClassName('modal-backdrop')[0]) { 195 document.getElementsByClassName('modal-backdrop')[0].remove(); 196 } 197 } else { 198 openLyric.style.opacity = "0.3"; 199 openLyric.removeAttribute('data-toggle'); 200 } 201 } catch (error) { 202 console.log("Erro ao buscar a letra da música:", error); 203 openLyric.style.opacity = "0.3"; 204 openLyric.removeAttribute('data-toggle'); 205 } 206 }; 207 } 208} 209 210 211async function getStreamingData() { 212 try { 213 let data = await fetchStreamingData(API_URL); 214 if (!data) { 215 data = await fetchStreamingData(FALLBACK_API_URL); 216 } 217 218 if (data) { 219 const page = new Page(); 220 const currentSong = data.songtitle || (typeof data.song === "object" ? data.song.title : data.song); 221 const currentArtist = typeof data.artist === "object" ? data.artist.title : data.artist; 222 223 const safeCurrentSong = (currentSong || "").replace(/'/g, "'").replace(/&/g, "&"); 224 const safeCurrentArtist = (currentArtist || "").replace(/'/g, "'").replace(/&/g, "&"); 225 226 if (safeCurrentSong !== musicaAtual) { 227 document.title = `${safeCurrentSong} - ${safeCurrentArtist} | ${RADIO_NAME}`; 228 229 page.refreshCover(safeCurrentSong, safeCurrentArtist); 230 page.refreshCurrentSong(safeCurrentSong, safeCurrentArtist); 231 page.refreshLyric(safeCurrentSong, safeCurrentArtist); 232 233 const historicContainer = document.getElementById("historicSong"); 234 historicContainer.innerHTML = ""; 235 236 const historyArray = data.song_history 237 ? data.song_history.map((item) => ({ song: item.song.title, artist: item.song.artist })) 238 : data.history;
239 240 const maxSongsToDisplay = 4; // Adjust as needed 241 const limitedHistory = historyArray.slice(Math.max(0, historyArray.length - maxSongsToDisplay)); 242 243 for (let i = 0; i < limitedHistory.length; i++) { 244 const songInfo = limitedHistory[i]; 245 const article = document.createElement("article"); 246 article.classList.add("col-12", "col-md-6"); 247 article.innerHTML = ` 248 <div class="cover-historic" style="background-image: url('img/cover.png');"></div> 249 <div class="music-info"> 250 <p class="song">${songInfo.song || "Desconhecido"}</p> 251 <p class="artist">${songInfo.artist || "Desconhecido"}</p> 252 </div> 253 `; 254 historicContainer.appendChild(article); 255 try { 256 page.refreshHistoric(songInfo, i); 257 } catch (error) { 258 console.error("Error refreshing historic song:", error); 259 } 260 } 261 musicaAtual = safeCurrentSong; 262 } 263 } 264 } catch (error) { 265 console.log("Erro ao buscar dados de streaming:", error); 266 } 267} 268 269 270// Função para buscar dados de streaming de uma API especÃfica 271async function fetchStreamingData(apiUrl) { 272 try { 273 const response = await fetch(apiUrl); 274 if (!response.ok) { 275 throw new Error(`Erro na requisição da API: ${response.status} ${response.statusText}`); 276 } 277 278 const data = await response.json(); 279 return data; 280 } catch (error) { 281 console.log("Erro ao buscar dados de streaming da API:", error); 282 return null; // Retorna null em caso de erro 283 } 284} 285 286// Função para alterar o tamanho da imagem do iTunes 287function changeImageSize(url, size) { 288 const parts = url.split("/"); 289 const filename = parts.pop(); 290 const newFilename = `${size}${filename.substring(filename.lastIndexOf("."))}`; 291 return parts.join("/") + "/" + newFilename; 292} 293 294// Função para buscar dados da API do iTunes 295const getDataFromITunes = async (artist, title, defaultArt, defaultCover) => { 296 let text; 297 if (artist === title) { 298 text = `${title}`; 299 } else { 300 text = `${artist} - ${title}`; 301 } 302 const cacheKey = text.toLowerCase(); 303 if (cache[cacheKey]) { 304 return cache[cacheKey]; 305 } 306 307 const response = await fetch(`https://itunes.apple.com/search?limit=1&term=${encodeURIComponent(text)}`); 308 if (response.status === 403) { 309 const results = { 310 title, 311 artist, 312 art: defaultArt, 313 cover: defaultCover, 314 stream_url: "#not-found", 315 }; 316 return results; 317 } 318 const data = response.ok ? await response.json() : {}; 319 if (!data.results || data.results.length === 0) { 320 const results = { 321 title, 322 artist, 323 art: defaultArt, 324 cover: defaultCover, 325 stream_url: "#not-found", 326 }; 327 return results; 328 } 329 const itunes = data.results[0]; 330 const results = { 331 title: title, // Mantive o tÃtulo original da transmissão 332 artist: artist, // Mantive o artista original da transmissão 333 thumbnail: itunes.artworkUrl100 || defaultArt, 334 art: itunes.artworkUrl100 ? changeImageSize(itunes.artworkUrl100, "600x600") : defaultArt, 335 cover: itunes.artworkUrl100 ? changeImageSize(itunes.artworkUrl100, "1500x1500") : defaultCover, 336 stream_url: "#not-found", 337 }; 338 cache[cacheKey] = results; 339 return results; 340}; 341 342// AUDIO 343 344 345// Variável global para armazenar as músicas 346var audio = new Audio(URL_STREAMING); 347 348// Player control 349class Player { 350 constructor() { 351 this.play = function () { 352 audio.play(); 353 354 var defaultVolume = document.getElementById('volume').value; 355 356 if (typeof (Storage) !== 'undefined') { 357 if (localStorage.getItem('volume') !== null) { 358 audio.volume = intToDecimal(localStorage.getItem('volume'));
359 } else { 360 audio.volume = intToDecimal(defaultVolume); 361 } 362 } else { 363 audio.volume = intToDecimal(defaultVolume); 364 } 365 document.getElementById('volIndicator').innerHTML = defaultVolume; 366 367 togglePlay(); // Adiciona esta linha para atualizar o botão 368 }; 369 370 this.pause = function () { 371 audio.pause(); 372 }; 373 } 374} 375 376// On play, change the button to pause 377audio.onplay = function () { 378 var botao = document.getElementById('playerButton'); 379 var bplay = document.getElementById('buttonPlay'); 380 if (botao.className === 'fa fa-play') { 381 botao.className = 'fa fa-pause'; 382 bplay.firstChild.data = 'PAUSAR'; 383 } 384} 385 386// On pause, change the button to play 387audio.onpause = function () { 388 var botao = document.getElementById('playerButton'); 389 var bplay = document.getElementById('buttonPlay'); 390 if (botao.className === 'fa fa-pause') { 391 botao.className = 'fa fa-play'; 392 bplay.firstChild.data = 'PLAY'; 393 } 394} 395 396// Unmute when volume changed 397audio.onvolumechange = function () { 398 if (audio.volume > 0) { 399 audio.muted = false; 400 } 401} 402 403audio.onerror = function () { 404 var confirmacao = confirm('Stream Down / Network Error. \nClick OK to try again.'); 405 406 if (confirmacao) { 407 window.location.reload(); 408 } 409} 410 411document.getElementById('volume').oninput = function () { 412 audio.volume = intToDecimal(this.value); 413 414 var page = new Page(); 415 page.changeVolumeIndicator(this.value); 416} 417 418 419function togglePlay() { 420 const playerButton = document.getElementById("playerButton"); 421 const isPlaying = playerButton.classList.contains("fa-pause-circle"); 422 423 if (isPlaying) { 424 playerButton.classList.remove("fa-pause-circle"); 425 playerButton.classList.add("fa-play-circle"); 426 playerButton.style.textShadow = "0 0 5px black"; 427 audio.pause(); 428 } else { 429 playerButton.classList.remove("fa-play-circle"); 430 playerButton.classList.add("fa-pause-circle"); 431 playerButton.style.textShadow = "0 0 5px black"; 432 audio.load(); 433 audio.play(); 434 } 435 } 436 437function volumeUp() { 438 var vol = audio.volume; 439 if(audio) { 440 if(audio.volume >= 0 && audio.volume < 1) { 441 audio.volume = (vol + .01).toFixed(2); 442 } 443 } 444} 445 446function volumeDown() { 447 var vol = audio.volume; 448 if(audio) { 449 if(audio.volume >= 0.01 && audio.volume <= 1) { 450 audio.volume = (vol - .01).toFixed(2); 451 } 452 } 453} 454 455function mute() { 456 if (!audio.muted) { 457 document.getElementById('volIndicator').innerHTML = 0; 458 document.getElementById('volume').value = 0; 459 audio.volume = 0; 460 audio.muted = true; 461 } else { 462 var localVolume = localStorage.getItem('volume'); 463 document.getElementById('volIndicator').innerHTML = localVolume; 464 document.getElementById('volume').value = localVolume; 465 audio.volume = intToDecimal(localVolume); 466 audio.muted = false; 467 } 468} 469 470document.addEventListener('keydown', function (event) { 471 var key = event.key; 472 var slideVolume = document.getElementById('volume'); 473 var page = new Page(); 474 475 switch (key) { 476 // Arrow up 477 case 'ArrowUp': 478 volumeUp(); 479 slideVolume.value = decimalToInt(audio.volume); 480 page.changeVolumeIndicator(decimalToInt(audio.volume)); 481 break; 482 // Arrow down 483 case 'ArrowDown': 484 volumeDown(); 485 slideVolume.value = decimalToInt(audio.volume); 486 page.changeVolumeIndicator(decimalToInt(audio.volume)); 487 break; 488 // Spacebar 489 case ' ': 490 case 'Spacebar': 491 togglePlay(); 492 break; 493 // P 494 case 'p': 495 case 'P': 496 togglePlay(); 497 break; 498 // M 499 case 'm': 500 case 'M': 501 mute(); 502 break; 503 // Numeric keys 0-9 504 case '0': 505 case '1': 506 case '2': 507 case '3': 508 case '4': 509 case '5': 510 case '6': 511 case '7': 512 case '8': 513 case '9': 514 var volumeValue = parseInt(key); 515 audio.volume = volumeValue / 10; 516 slideVolume.value = volumeValue * 10; 517 page.changeVolumeIndicator(volumeValue * 10); 518 break; 519 } 520}); 521 522function intToDecimal(vol) { 523 return vol / 100; 524} 525 526function decimalToInt(vol) { 527 return vol * 100; 528}
Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.