304 lines
8.4 KiB
JavaScript
304 lines
8.4 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_songListSection = document.getElementById("song-list");
|
|
|
|
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) + "%"
|
|
);
|
|
|
|
e_songListSection.style = `background-image: url(${radioData.now_playing.song.art})`;
|
|
|
|
// 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 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 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) {
|
|
e_player.classList.remove("paused");
|
|
// 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;
|
|
}
|
|
|
|
function pauseRadio() {
|
|
if (!isPlaying) {
|
|
return;
|
|
}
|
|
|
|
e_player.classList.add("paused");
|
|
progressStep = 0;
|
|
e_channels.forEach((el) => el.classList.remove("active"));
|
|
document.getElementById("pause").classList.add("active");
|
|
e_audio.pause();
|
|
|
|
isPlaying = false;
|
|
}
|
|
|
|
function toggleMute() {
|
|
if (e_audio.muted) {
|
|
document.getElementById("mute").classList.remove("active");
|
|
e_volumeBar.classList.remove("muted");
|
|
e_audio.muted = false;
|
|
} else {
|
|
document.getElementById("mute").classList.add("active");
|
|
e_volumeBar.classList.add("muted");
|
|
e_audio.muted = 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);
|