722 líneas
18 KiB
JavaScript
722 líneas
18 KiB
JavaScript
const RADIO_NAME = "Afrofusion Rap"
|
|
|
|
// Change Stream URL Here, Supports, ICECAST, ZENO, SHOUTCAST, RADIOJAR ETC.... DOES NOT SUPPORT HLS
|
|
const URL_STREAMING = "https://radio15.servidorderadio.net/8999/stream"
|
|
|
|
// NEW UNIFIED API ENDPOINT
|
|
const API_URL = "https://prod-api.radioapi.me/metadata/92f09a6d-37f1-4c10-bf2a-018bf03136fc"
|
|
|
|
// Loading state management
|
|
let isInitialLoad = true
|
|
let isDataLoaded = false
|
|
|
|
// Store current song data to prevent unnecessary updates
|
|
const currentSongData = {
|
|
song: "",
|
|
artist: "",
|
|
}
|
|
|
|
|
|
window.onload = () => {
|
|
|
|
|
|
var page = new Page()
|
|
page.changeTitlePage()
|
|
page.setVolume()
|
|
|
|
var player = new Player()
|
|
player.play()
|
|
|
|
// Initialize history items
|
|
initializeHistoryItems()
|
|
|
|
// Initial data fetch
|
|
getStreamingData()
|
|
|
|
// Interval to get streaming data in milliseconds
|
|
setInterval(() => {
|
|
getStreamingData()
|
|
}, 10000)
|
|
}
|
|
|
|
|
|
|
|
// Loading state helpers
|
|
function showArtworkLoading() {
|
|
const artwork = document.getElementById("currentCoverArt")
|
|
if (artwork) {
|
|
artwork.classList.add("loading")
|
|
}
|
|
}
|
|
|
|
function hideArtworkLoading() {
|
|
const artwork = document.getElementById("currentCoverArt")
|
|
if (artwork) {
|
|
artwork.classList.remove("loading")
|
|
}
|
|
}
|
|
|
|
function showButtonLoading() {
|
|
const button = document.querySelector(".btn-play")
|
|
if (button) {
|
|
button.classList.add("loading")
|
|
}
|
|
}
|
|
|
|
function hideButtonLoading() {
|
|
const button = document.querySelector(".btn-play")
|
|
if (button) {
|
|
button.classList.remove("loading")
|
|
}
|
|
}
|
|
|
|
function showLyricsLoading() {
|
|
const lyrics = document.querySelector(".lyrics")
|
|
if (lyrics) {
|
|
lyrics.classList.add("loading")
|
|
}
|
|
}
|
|
|
|
function hideLyricsLoading() {
|
|
const lyrics = document.querySelector(".lyrics")
|
|
if (lyrics) {
|
|
lyrics.classList.remove("loading")
|
|
}
|
|
}
|
|
|
|
function showHistoryLoading() {
|
|
const covers = document.querySelectorAll(".cover-historic")
|
|
covers.forEach((cover) => {
|
|
cover.classList.add("loading")
|
|
})
|
|
}
|
|
|
|
function hideHistoryLoading() {
|
|
const covers = document.querySelectorAll(".cover-historic")
|
|
covers.forEach((cover) => {
|
|
cover.classList.remove("loading")
|
|
})
|
|
}
|
|
|
|
// DOM control
|
|
function Page() {
|
|
this.changeTitlePage = (title = RADIO_NAME) => {
|
|
document.title = title
|
|
}
|
|
|
|
this.refreshCurrentSong = (song, artist) => {
|
|
var currentSong = document.getElementById("currentSong")
|
|
var currentArtist = document.getElementById("currentArtist")
|
|
|
|
if (currentSong && currentArtist) {
|
|
// Remove skeleton loading
|
|
const songSkeleton = currentSong.querySelector(".song-skeleton")
|
|
const artistSkeleton = currentArtist.querySelector(".artist-skeleton")
|
|
|
|
if (songSkeleton) songSkeleton.remove()
|
|
if (artistSkeleton) artistSkeleton.remove()
|
|
|
|
// Animate transition
|
|
currentSong.className = "animated flipInY current-song"
|
|
currentSong.innerHTML = song
|
|
|
|
currentArtist.className = "animated flipInY current-artist"
|
|
currentArtist.innerHTML = artist
|
|
|
|
// Refresh modal title
|
|
const lyricsSong = document.getElementById("lyricsSong")
|
|
if (lyricsSong) {
|
|
lyricsSong.innerHTML = song + " - " + artist
|
|
}
|
|
|
|
// Remove animation classes
|
|
setTimeout(() => {
|
|
currentSong.className = "current-song"
|
|
currentArtist.className = "current-artist"
|
|
}, 2000)
|
|
}
|
|
}
|
|
|
|
this.refreshHistoric = (info, n) => {
|
|
var $historicDiv = document.querySelectorAll("#modalHistory .history-item")
|
|
var $songName = document.querySelectorAll("#modalHistory .history-item .music-info .song")
|
|
var $artistName = document.querySelectorAll("#modalHistory .history-item .music-info .artist")
|
|
|
|
// Show loading for this specific item
|
|
if ($historicDiv[n]) {
|
|
const cover = $historicDiv[n].querySelector(".cover-historic")
|
|
if (cover) cover.classList.add("loading")
|
|
}
|
|
|
|
// Use artwork directly from API if available
|
|
if (info.artwork && $historicDiv[n]) {
|
|
const cover = $historicDiv[n].querySelector(".cover-historic")
|
|
if (cover) {
|
|
cover.style.backgroundImage = "url(" + info.artwork + ")"
|
|
cover.classList.remove("loading")
|
|
}
|
|
}
|
|
|
|
// Formating characters to UTF-8
|
|
var music = info.song.replace(/'/g, "'")
|
|
var songHist = music.replace(/&/g, "&")
|
|
|
|
var artist = info.artist.replace(/'/g, "'")
|
|
var artistHist = artist.replace(/&/g, "&")
|
|
|
|
// Remove skeleton loading
|
|
if ($songName[n]) {
|
|
const songSkeleton = $songName[n].querySelector(".history-song-skeleton")
|
|
if (songSkeleton) songSkeleton.remove()
|
|
$songName[n].innerHTML = songHist
|
|
}
|
|
|
|
if ($artistName[n]) {
|
|
const artistSkeleton = $artistName[n].querySelector(".history-artist-skeleton")
|
|
if (artistSkeleton) artistSkeleton.remove()
|
|
$artistName[n].innerHTML = artistHist
|
|
}
|
|
|
|
// Add class for animation
|
|
if ($historicDiv[n]) {
|
|
$historicDiv[n].classList.add("animated")
|
|
$historicDiv[n].classList.add("slideInRight")
|
|
}
|
|
|
|
setTimeout(() => {
|
|
for (var j = 0; j < 5; j++) {
|
|
if ($historicDiv[j]) {
|
|
$historicDiv[j].classList.remove("animated")
|
|
$historicDiv[j].classList.remove("slideInRight")
|
|
}
|
|
}
|
|
}, 2000)
|
|
}
|
|
|
|
this.refreshCover = (artworkUrl, song, artist) => {
|
|
// Show artwork loading
|
|
showArtworkLoading()
|
|
|
|
var coverArt = document.getElementById("currentCoverArt")
|
|
var coverBackground = document.getElementById("bgCover")
|
|
|
|
if (artworkUrl && coverArt) {
|
|
coverArt.style.backgroundImage = "url(" + artworkUrl + ")"
|
|
coverArt.className = "animated bounceInLeft"
|
|
|
|
if (coverBackground) {
|
|
coverBackground.style.backgroundImage = "url(" + artworkUrl + ")"
|
|
}
|
|
|
|
// Hide artwork loading
|
|
setTimeout(() => {
|
|
hideArtworkLoading()
|
|
coverArt.className = ""
|
|
}, 2000)
|
|
|
|
// Update media session
|
|
if ("mediaSession" in navigator) {
|
|
navigator.mediaSession.metadata = new MediaMetadata({
|
|
title: song,
|
|
artist: artist,
|
|
artwork: [
|
|
{
|
|
src: artworkUrl,
|
|
sizes: "96x96",
|
|
type: "image/png",
|
|
},
|
|
{
|
|
src: artworkUrl,
|
|
sizes: "128x128",
|
|
type: "image/png",
|
|
},
|
|
{
|
|
src: artworkUrl,
|
|
sizes: "192x192",
|
|
type: "image/png",
|
|
},
|
|
{
|
|
src: artworkUrl,
|
|
sizes: "256x256",
|
|
type: "image/png",
|
|
},
|
|
{
|
|
src: artworkUrl,
|
|
sizes: "384x384",
|
|
type: "image/png",
|
|
},
|
|
{
|
|
src: artworkUrl,
|
|
sizes: "512x512",
|
|
type: "image/png",
|
|
},
|
|
],
|
|
})
|
|
}
|
|
} else {
|
|
// Hide loading even if no artwork
|
|
hideArtworkLoading()
|
|
}
|
|
}
|
|
|
|
this.changeVolumeIndicator = (volume) => {
|
|
const volIndicator = document.getElementById("volIndicator")
|
|
if (volIndicator) {
|
|
volIndicator.innerHTML = volume
|
|
}
|
|
|
|
if (typeof Storage !== "undefined") {
|
|
localStorage.setItem("volume", volume)
|
|
}
|
|
}
|
|
|
|
this.setVolume = () => {
|
|
if (typeof Storage !== "undefined") {
|
|
var volumeLocalStorage = !localStorage.getItem("volume") ? 80 : localStorage.getItem("volume")
|
|
const volumeSlider = document.getElementById("volume")
|
|
const volIndicator = document.getElementById("volIndicator")
|
|
|
|
if (volumeSlider) {
|
|
volumeSlider.value = volumeLocalStorage
|
|
}
|
|
if (volIndicator) {
|
|
volIndicator.innerHTML = volumeLocalStorage
|
|
}
|
|
}
|
|
}
|
|
|
|
this.refreshLyric = (lyrics) => {
|
|
// Show lyrics loading
|
|
showLyricsLoading()
|
|
|
|
var openLyric = document.getElementsByClassName("lyrics")[0]
|
|
|
|
// Hide lyrics loading
|
|
setTimeout(() => {
|
|
hideLyricsLoading()
|
|
}, 500)
|
|
|
|
if (lyrics && lyrics.trim() !== "") {
|
|
// Remove skeleton loading from modal
|
|
const lyricsLoading = document.querySelector(".lyrics-loading")
|
|
if (lyricsLoading) lyricsLoading.style.display = "none"
|
|
|
|
const lyricElement = document.getElementById("lyric")
|
|
if (lyricElement) {
|
|
lyricElement.innerHTML = lyrics.replace(/\n/g, "<br />")
|
|
}
|
|
|
|
if (openLyric) {
|
|
openLyric.style.opacity = "1"
|
|
openLyric.setAttribute("data-toggle", "modal")
|
|
}
|
|
} else {
|
|
if (openLyric) {
|
|
openLyric.style.opacity = "0.3"
|
|
openLyric.removeAttribute("data-toggle")
|
|
}
|
|
|
|
var modalLyric = document.getElementById("modalLyrics")
|
|
if (modalLyric) {
|
|
modalLyric.style.display = "none"
|
|
modalLyric.setAttribute("aria-hidden", "true")
|
|
}
|
|
|
|
const backdrop = document.getElementsByClassName("modal-backdrop")[0]
|
|
if (backdrop) {
|
|
backdrop.remove()
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
var audio = new Audio(URL_STREAMING)
|
|
|
|
// Player control
|
|
function Player() {
|
|
this.play = () => {
|
|
// Show button loading
|
|
showButtonLoading()
|
|
|
|
audio.play()
|
|
|
|
const volumeSlider = document.getElementById("volume")
|
|
var defaultVolume = volumeSlider ? volumeSlider.value : 80
|
|
|
|
if (typeof Storage !== "undefined") {
|
|
if (localStorage.getItem("volume") !== null) {
|
|
audio.volume = intToDecimal(localStorage.getItem("volume"))
|
|
} else {
|
|
audio.volume = intToDecimal(defaultVolume)
|
|
}
|
|
} else {
|
|
audio.volume = intToDecimal(defaultVolume)
|
|
}
|
|
|
|
const volIndicator = document.getElementById("volIndicator")
|
|
if (volIndicator) {
|
|
volIndicator.innerHTML = defaultVolume
|
|
}
|
|
|
|
// Hide button loading after a short delay
|
|
setTimeout(() => {
|
|
hideButtonLoading()
|
|
}, 1000)
|
|
}
|
|
|
|
this.pause = () => {
|
|
audio.pause()
|
|
}
|
|
}
|
|
|
|
// On play, change the button to pause
|
|
audio.onplay = () => {
|
|
var botao = document.getElementById("playerButton")
|
|
var bplay = document.getElementById("buttonPlay")
|
|
if (botao && botao.className === "fa fa-play") {
|
|
botao.className = "fa fa-pause"
|
|
if (bplay) bplay.innerHTML = "PAUSE"
|
|
}
|
|
hideButtonLoading()
|
|
}
|
|
|
|
// On pause, change the button to play
|
|
audio.onpause = () => {
|
|
var botao = document.getElementById("playerButton")
|
|
var bplay = document.getElementById("buttonPlay")
|
|
if (botao && botao.className === "fa fa-pause") {
|
|
botao.className = "fa fa-play"
|
|
if (bplay) bplay.innerHTML = "PLAY"
|
|
}
|
|
hideButtonLoading()
|
|
}
|
|
|
|
// Unmute when volume changed
|
|
audio.onvolumechange = () => {
|
|
if (audio.volume > 0) {
|
|
audio.muted = false
|
|
}
|
|
}
|
|
|
|
audio.onerror = () => {
|
|
hideButtonLoading()
|
|
hideArtworkLoading()
|
|
var confirmacao = confirm("Stream Down / Network Error. \nClick OK to try again.")
|
|
|
|
if (confirmacao) {
|
|
window.location.reload()
|
|
}
|
|
}
|
|
|
|
// Volume control
|
|
const volumeSlider = document.getElementById("volume")
|
|
if (volumeSlider) {
|
|
volumeSlider.oninput = function () {
|
|
audio.volume = intToDecimal(this.value)
|
|
|
|
var page = new Page()
|
|
page.changeVolumeIndicator(this.value)
|
|
}
|
|
}
|
|
|
|
function togglePlay() {
|
|
if (!audio.paused) {
|
|
audio.pause()
|
|
} else {
|
|
showButtonLoading()
|
|
audio.load()
|
|
audio.play()
|
|
}
|
|
}
|
|
|
|
function volumeUp() {
|
|
var vol = audio.volume
|
|
if (audio) {
|
|
if (audio.volume >= 0 && audio.volume < 1) {
|
|
audio.volume = (vol + 0.01).toFixed(2)
|
|
}
|
|
}
|
|
}
|
|
|
|
function volumeDown() {
|
|
var vol = audio.volume
|
|
if (audio) {
|
|
if (audio.volume >= 0.01 && audio.volume <= 1) {
|
|
audio.volume = (vol - 0.01).toFixed(2)
|
|
}
|
|
}
|
|
}
|
|
|
|
function mute() {
|
|
const volIndicator = document.getElementById("volIndicator")
|
|
const volumeSlider = document.getElementById("volume")
|
|
|
|
if (!audio.muted) {
|
|
if (volIndicator) volIndicator.innerHTML = 0
|
|
if (volumeSlider) volumeSlider.value = 0
|
|
audio.volume = 0
|
|
audio.muted = true
|
|
} else {
|
|
var localVolume = localStorage.getItem("volume") || 80
|
|
if (volIndicator) volIndicator.innerHTML = localVolume
|
|
if (volumeSlider) volumeSlider.value = localVolume
|
|
audio.volume = intToDecimal(localVolume)
|
|
audio.muted = false
|
|
}
|
|
}
|
|
|
|
function getStreamingData() {
|
|
var xhttp = new XMLHttpRequest()
|
|
xhttp.onreadystatechange = function () {
|
|
if (this.readyState === 4 && this.status === 200) {
|
|
if (this.response.length === 0) {
|
|
console.log("%cdebug", "font-size: 22px")
|
|
return
|
|
}
|
|
|
|
try {
|
|
var data = JSON.parse(this.responseText)
|
|
console.log("Received data:", data)
|
|
|
|
var page = new Page()
|
|
|
|
// Extract current song info from unified API
|
|
const song = data.song || ""
|
|
const artist = data.artist || ""
|
|
|
|
// Change the title
|
|
document.title = song + " - " + artist + " | " + RADIO_NAME
|
|
|
|
// Compare with stored data instead of innerHTML to prevent unnecessary updates
|
|
if (currentSongData.song !== song || currentSongData.artist !== artist) {
|
|
// Update stored data
|
|
currentSongData.song = song
|
|
currentSongData.artist = artist
|
|
|
|
// Update current song info
|
|
page.refreshCurrentSong(song, artist)
|
|
|
|
// Update artwork using the direct URL from API
|
|
if (data.artwork) {
|
|
page.refreshCover(data.artwork, song, artist)
|
|
}
|
|
|
|
// Update lyrics
|
|
page.refreshLyric(data.lyrics || "")
|
|
|
|
// Update history if available
|
|
if (data.history && data.history.length > 0) {
|
|
for (var i = 0; i < Math.min(5, data.history.length); i++) {
|
|
page.refreshHistoric(data.history[i], i)
|
|
}
|
|
}
|
|
}
|
|
|
|
isDataLoaded = true
|
|
} catch (error) {
|
|
console.error("Error parsing API response:", error)
|
|
}
|
|
}
|
|
}
|
|
|
|
// Make the API request
|
|
xhttp.open("GET", API_URL)
|
|
xhttp.send()
|
|
}
|
|
|
|
// Player control by keys
|
|
document.addEventListener("keydown", (event) => {
|
|
var key = event.keyCode || event.which
|
|
|
|
var slideVolume = document.getElementById("volume")
|
|
var page = new Page()
|
|
|
|
switch (key) {
|
|
// Arrow up
|
|
case 38:
|
|
volumeUp()
|
|
if (slideVolume) slideVolume.value = decimalToInt(audio.volume)
|
|
page.changeVolumeIndicator(decimalToInt(audio.volume))
|
|
break
|
|
// Arrow down
|
|
case 40:
|
|
volumeDown()
|
|
if (slideVolume) slideVolume.value = decimalToInt(audio.volume)
|
|
page.changeVolumeIndicator(decimalToInt(audio.volume))
|
|
break
|
|
// Spacebar
|
|
case 32:
|
|
togglePlay()
|
|
break
|
|
// P
|
|
case 80:
|
|
togglePlay()
|
|
break
|
|
// M
|
|
case 77:
|
|
mute()
|
|
break
|
|
// 0
|
|
case 48:
|
|
audio.volume = 0
|
|
if (slideVolume) slideVolume.value = 0
|
|
page.changeVolumeIndicator(0)
|
|
break
|
|
// 0 numeric keyboard
|
|
case 96:
|
|
audio.volume = 0
|
|
if (slideVolume) slideVolume.value = 0
|
|
page.changeVolumeIndicator(0)
|
|
break
|
|
// 1
|
|
case 49:
|
|
audio.volume = 0.1
|
|
if (slideVolume) slideVolume.value = 10
|
|
page.changeVolumeIndicator(10)
|
|
break
|
|
// 1 numeric key
|
|
case 97:
|
|
audio.volume = 0.1
|
|
if (slideVolume) slideVolume.value = 10
|
|
page.changeVolumeIndicator(10)
|
|
break
|
|
// 2
|
|
case 50:
|
|
audio.volume = 0.2
|
|
if (slideVolume) slideVolume.value = 20
|
|
page.changeVolumeIndicator(20)
|
|
break
|
|
// 2 numeric key
|
|
case 98:
|
|
audio.volume = 0.2
|
|
if (slideVolume) slideVolume.value = 20
|
|
page.changeVolumeIndicator(20)
|
|
break
|
|
// 3
|
|
case 51:
|
|
audio.volume = 0.3
|
|
if (slideVolume) slideVolume.value = 30
|
|
page.changeVolumeIndicator(30)
|
|
break
|
|
// 3 numeric key
|
|
case 99:
|
|
audio.volume = 0.3
|
|
if (slideVolume) slideVolume.value = 30
|
|
page.changeVolumeIndicator(30)
|
|
break
|
|
// 4
|
|
case 52:
|
|
audio.volume = 0.4
|
|
if (slideVolume) slideVolume.value = 40
|
|
page.changeVolumeIndicator(40)
|
|
break
|
|
// 4 numeric key
|
|
case 100:
|
|
audio.volume = 0.4
|
|
if (slideVolume) slideVolume.value = 40
|
|
page.changeVolumeIndicator(40)
|
|
break
|
|
// 5
|
|
case 53:
|
|
audio.volume = 0.5
|
|
if (slideVolume) slideVolume.value = 50
|
|
page.changeVolumeIndicator(50)
|
|
break
|
|
// 5 numeric key
|
|
case 101:
|
|
audio.volume = 0.5
|
|
if (slideVolume) slideVolume.value = 50
|
|
page.changeVolumeIndicator(50)
|
|
break
|
|
// 6
|
|
case 54:
|
|
audio.volume = 0.6
|
|
if (slideVolume) slideVolume.value = 60
|
|
page.changeVolumeIndicator(60)
|
|
break
|
|
// 6 numeric key
|
|
case 102:
|
|
audio.volume = 0.6
|
|
if (slideVolume) slideVolume.value = 60
|
|
page.changeVolumeIndicator(60)
|
|
break
|
|
// 7
|
|
case 55:
|
|
audio.volume = 0.7
|
|
if (slideVolume) slideVolume.value = 70
|
|
page.changeVolumeIndicator(70)
|
|
break
|
|
// 7 numeric key
|
|
case 103:
|
|
audio.volume = 0.7
|
|
if (slideVolume) slideVolume.value = 70
|
|
page.changeVolumeIndicator(70)
|
|
break
|
|
// 8
|
|
case 56:
|
|
audio.volume = 0.8
|
|
if (slideVolume) slideVolume.value = 80
|
|
page.changeVolumeIndicator(80)
|
|
break
|
|
// 8 numeric key
|
|
case 104:
|
|
audio.volume = 0.8
|
|
if (slideVolume) slideVolume.value = 80
|
|
page.changeVolumeIndicator(80)
|
|
break
|
|
// 9
|
|
case 57:
|
|
audio.volume = 0.9
|
|
if (slideVolume) slideVolume.value = 90
|
|
page.changeVolumeIndicator(90)
|
|
break
|
|
// 9 numeric key
|
|
case 105:
|
|
audio.volume = 0.9
|
|
if (slideVolume) slideVolume.value = 90
|
|
page.changeVolumeIndicator(90)
|
|
break
|
|
}
|
|
})
|
|
|
|
function intToDecimal(vol) {
|
|
return vol / 100
|
|
}
|
|
|
|
function decimalToInt(vol) {
|
|
return vol * 100
|
|
}
|
|
|
|
// Initialize history items dynamically
|
|
function initializeHistoryItems() {
|
|
const historyContainer = document.getElementById("historicSong")
|
|
const HISTORY_ITEMS_COUNT = 5
|
|
|
|
if (historyContainer) {
|
|
// Clear any existing items
|
|
historyContainer.innerHTML = ""
|
|
|
|
// Generate history items
|
|
for (let i = 0; i < HISTORY_ITEMS_COUNT; i++) {
|
|
const historyItem = document.createElement("article")
|
|
historyItem.className = "history-item"
|
|
|
|
historyItem.innerHTML = `
|
|
<div class="cover-historic">
|
|
<div class="cover-loading-spinner"></div>
|
|
</div>
|
|
<div class="music-info">
|
|
<div class="song">
|
|
<span class="history-song-skeleton">Loading song...</span>
|
|
</div>
|
|
<div class="artist">
|
|
<span class="history-artist-skeleton">Loading artist...</span>
|
|
</div>
|
|
</div>
|
|
`
|
|
|
|
historyContainer.appendChild(historyItem)
|
|
}
|
|
}
|
|
}
|