Replaced cover.png with cover.jpeg and updated CSS references. Changed radio name, streaming URL, and API endpoint in script.js. Removed Vagalume lyrics API integration in favor of using lyrics from RadioAPI.me, simplifying the lyrics fetching logic.
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://play.streamafrica.net/afrofusionrap"
|
|
|
|
// 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)
|
|
}
|
|
}
|
|
}
|