scuf-radio-frontend/public/js/index.js
2024-11-21 21:04:05 +03:00

300 lines
8.2 KiB
JavaScript

// HTML Elements
const e_songDetails = {
title: document.getElementById("current-title"),
playlist: document.getElementById("current-playlist"),
artist: document.getElementById("current-artist"),
}
const e_playlist = document.getElementById("current-playlist")
const e_songList = {
next: document.getElementById("list-next"),
current: document.getElementById("list-current"),
previous: document.getElementById("list-previous"),
}
const e_listeners = {
current: document.querySelector(".listeners.current"),
total: document.querySelector(".listeners.total"),
unique: document.querySelector(".listeners.unique"),
}
const e_audio = document.getElementById("audio");
e_audio.volume = 0.2;
const e_warningMessage = document.getElementById("warning-message");
const e_player = document.getElementById("player");
const e_progressBar = document.getElementById("progress-bar");
const e_volumeBar = document.getElementById("volume-bar");
const e_channels = document.querySelectorAll(".channel");
/**
* Return a random integer between 0 and a given limit.
* @param {int} limit - The maximum returnable integer
*/
const rRng=(limit)=> Math.floor(Math.random() * limit);
/**
* Return a random item from a given array.
* @param {Array} array - The maximum returnable integer
*/
const rArr=(array)=> array[rRng(array.length)];
// :^)
const lainon = {
life: "https://radio.scuf.ru/",
}
// Set a random background image
const backgrounds = [
"bg205.gif",
"bg_307.gif",
"bg_315.gif",
"patternTV2.gif",
];
document.body.style.setProperty(
"background-image",
`url("./public/asset/${rArr(backgrounds)}")`
);
// Default settings
let isPlaying = false;
let currentChannel = "cafe";
let progressStep = 0;
/**
* Send a GET request to lainon.life, then do stuff with the reponse.
* @returns {Object} data - Information about the present radio state
*/
async function fetchData() {
// Since lainon.life doesn't allow CORS, the request must be
// made through a proxy. This "crosscloak" is my fork of
// cors-anywhere, and just in case you're thinking about it,
// no, it's not public and your domain is not on the whitelist.
const data = await
fetch(`https://radio.scuf.ru/api/nowplaying/scuf_fm`)
.then((response)=> response.json())
.catch((ERROR)=> {
console.error(ERROR);
e_warningMessage.classList.remove("hidden");
});
parseData(data);
return data;
}
/**
* Parse the reponse data from lainon.life and display it on the page.
* @param {Object} radioData - Present state of the lainon.life radio.
*/
function parseData(radioData) {
// Update listeners
for (const listenerType in radioData.listeners) {
e_listeners[listenerType].innerText = radioData.listeners[listenerType]
}
// Go over the radioData object.
// If the object exists, set the corresponding element's
// innerText to its value, or otherwise to say that it
// wasn't found.
for (const itemType in e_songDetails) {
e_songDetails[itemType].innerText = (
(radioData.now_playing.song[itemType])
? radioData.now_playing.song[itemType]
: `${itemType.toUpperCase()} NOT FOUND`
);
}
e_playlist.innerText = radioData.now_playing.playlist
// Current song's length in seconds
const songLength = radioData.now_playing.duration;
// Increment for the progress bar as a percentage of 100
progressStep = (1/songLength) * 100;
// Set the progress bar to display the current song's elapsed time
e_progressBar.style.setProperty(
"width",
(progressStep * Math.floor(radioData.now_playing.elapsed)) + "%"
);
// Clear the entire song list
for (const element in e_songList)
e_songList[element].innerHTML = "";
// Fill in the upcoming songs
const {song,duration} = radioData.playing_next
const e_songEntry = document.createElement("div");
e_songEntry.className = "song-entry";
e_songEntry.innerHTML = `
<div class="entry-details">
<span>Next FLEX: </span>
<span class="entry-artist">${song.artist}</span>
-
<span class="entry-title">${song.title}</span>
</div>
<span class="entry-length">${toMinSec(duration).join(":")}</span>
`;
e_songList.next.appendChild(e_songEntry);
// Display the current song in the song list
e_songList.current.innerHTML = `
<div class="song-entry">
<div class="entry-details">
<span>Current FLEX: </span>
<span class="entry-artist">${radioData.now_playing.song.artist}</span>
-
<span class="entry-title">${radioData.now_playing.song.title}</span>
</div>
<span class="entry-length">${toMinSec(radioData.now_playing.duration).join(":")}</span>
</div>
`;
// Fill in the previous songs
for (const song of radioData.song_history) {
const e_songEntry = document.createElement("div");
e_songEntry.className = "song-entry";
e_songEntry.innerHTML = `
<div class="entry-details">
<span class="entry-artist">${song.song.artist}</span>
-
<span class="entry-title">${song.song.title}</span>
</div>
<span class="entry-length">${toMinSec(song.duration).join(":")}</span>
`;
e_songList.previous.appendChild(e_songEntry);
}
}
/**
* Convert seconds to minutes.
* @param {int} totalSeconds - Any amount of seconds
* @returns {Array} [minutes, seconds]
*/
function toMinSec(totalSeconds) {
return [
padZero(Math.floor(totalSeconds/60)),
padZero(totalSeconds % 60)
];
}
/**
* Prefixes single digits with a "0".
* @param {} integer
* @returns {String} Input number converted to a string
*/
function padZero(integer) {
return (integer < 10)
? "0" + integer
: "" + integer
}
/**
* Change to a different radio channel.
* @param {String} channelName
*/
function switchChannel(channelName) {
console.log(e_audio.paused,isPlaying)
if (e_audio.paused === false) {
document.getElementById(channelName).classList.remove("active");
e_audio.pause()
isPlaying = false
currentChannel = "cafe"
e_audio.src = undefined
return
}
// Remove active class from all DOM channel elements
e_channels.forEach((el)=> el.classList.remove("active"));
// Add it to the clicked one
document.getElementById(channelName).classList.add("active");
currentChannel = channelName;
e_audio.src = `https://radio.scuf.ru/listen/scuf_fm/radio.mp3`;
e_audio.play();
fetchData();
isPlaying = true;
}
// Change the volume
let volumeUpdate; // Interval container
function setVolume(mouseEvent) {
// If anything other than LMB is pressed, return early
if (mouseEvent.buttons != 1) return;
// Horizontal coordinate of the click
const clickPosition = mouseEvent.offsetX;
// Calculate new bar position and volume
let volumeBarWidth = (100 / mouseEvent.target.clientWidth) * clickPosition;
let audioVolume = (1 / mouseEvent.target.clientWidth) * clickPosition;
// Resize the volume bar to the new width
e_volumeBar.style.setProperty(
"width",
`${volumeBarWidth}%`
);
// Set the audio volume
e_audio.volume = audioVolume;
}
e_player.onmousemove = setVolume;
e_player.onmousedown = setVolume;
// Refresh radio data when the tab is focused
window.onfocus=()=> {
if (isPlaying) fetchData();
}
// Update the progress bar every second
const updateProgressBar = setInterval(()=> {
// Current percentage
const width = parseFloat(e_progressBar.style.width);
// Future percentage
const futureWidth = width + progressStep;
// If the future percentage doesn't exceed 100%
// increment the progress bar.
if (futureWidth < 100) {
e_progressBar.style.setProperty(
"width",
futureWidth + "%"
);
}
// Otherwise, if it does exceed 100%, that means
// the song has changed, so fetch the new data.
else if (futureWidth >= 100)
fetchData();
}, 1000);